From 7189ca202da06b41f8dd6fab8cf8bca07323a5de Mon Sep 17 00:00:00 2001 From: tak2-08 Date: Wed, 26 Aug 2026 10:45:58 +0000 Subject: [PATCH] =?UTF-8?q?feat(commands):=20ac.mjs=20=ED=86=B5=ED=95=A9?= =?UTF-8?q?=20=EB=94=94=EC=8A=A4=ED=8C=A8=EC=B2=98=20+=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=A4=91=EC=8B=AC=20=EA=B8=B0=EB=A1=9D=20=EC=9B=90?= =?UTF-8?q?=EC=B9=99=20(=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=95=84=EC=9D=B4?= =?UTF-8?q?=EB=94=94=EC=96=B4=20=EB=B0=98=EC=98=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 스킬 커맨드 레이어 — tools/ac.mjs 단일 디스패처 - export/import/current: 세션 내보내기·불러오기·포인터 (handoff 래퍼) - issue/learning/idea/note/todo/decision: frontmatter 자동 생성 entry 작성 (파일명 규약·decision NNNN·diary append 자동 처리, level 비우면 lite AI 배정) - history/search/index/validate: 기존 도구 래퍼 - 슬래시 별칭 (/ac-export, /ac-import, /ac-issue ...) 을 SKILL.md 양쪽에 표로 문서화 2. 결과 중심 기록 원칙 (사용자 아이디어 채택) - 도구 호출 로그를 기억에 추가하지 않음 — '결론 + refs(검증 링크)'만 저장 - schema.json에 optional refs 필드 추가 - docs/protocol.md에 원칙 섹션, ac.mjs learning 템플릿이 cause/fix/lesson+refs 형식 강제 - README 명령어 섹션에 원칙 요약 근거: 도구 사용 과정·출력 전문은 토큰 낭비. 다음 에이전트는 결론을 쓰거나 의심 시 refs로 직접 확인하면 되므로 중간 과정은 가치 없음. 버그의 repro는 과정 로그가 아니라 재현 레시피로 최소 유지. 검증: /tmp/ac-test 샌드박스에서 issue/learning/export/history 실측, node --check 통과, validate ok, T2Editor 언급 0건 --- .claude/skills/agent-shared-context/SKILL.md | 27 +++ README.md | 11 ++ agent-context/schema.json | 15 +- docs/protocol.md | 15 ++ skills/agent-shared-context/SKILL.md | 20 +++ tools/ac.mjs | 167 +++++++++++++++++++ 6 files changed, 254 insertions(+), 1 deletion(-) create mode 100755 tools/ac.mjs diff --git a/.claude/skills/agent-shared-context/SKILL.md b/.claude/skills/agent-shared-context/SKILL.md index 014035b..552ac45 100644 --- a/.claude/skills/agent-shared-context/SKILL.md +++ b/.claude/skills/agent-shared-context/SKILL.md @@ -117,6 +117,33 @@ node tools/agent-context-validate.mjs node tools/benchmark.mjs # synthetic 5/50/500, writes BENCHMARK.md ``` +## Commands (슬래시 별칭 → 단일 Bash 호출) + +스킬 커맨드는 `node tools/ac.mjs` 디스패처 하나로 통합 — 서브에이전트 불필요, 메인 에이전트가 직접 실행. + +| 슬래시 별칭 | 실제 명령 | 동작 | +|---|---|---| +| `/ac-export` | `node tools/ac.mjs export --session S --task "..." --done "a;b" --next "c"` | 세션 내보내기 (핸드오프 저장 + CURRENT.md 갱신) | +| `/ac-import` | `node tools/ac.mjs import [file]` | 세션 불러오기 (복원 브리프 ~280 tok) | +| `/ac-current` | `node tools/ac.mjs current` | 현재 포인터 보기 | +| `/ac-history` | `node tools/ac.mjs history "query" --limit 3` | 히스토리·지식 검색 (계층, 0 LLM) | +| `/ac-issue` | `node tools/ac.mjs issue --title "..." [--feature F] [--refs "p1,p2"]` | 이슈 작성 | +| `/ac-learning` | `node tools/ac.mjs learning --title "..." --cause C --fix F2 --lesson L` | 교훈 기록 | +| `/ac-idea` | `node tools/ac.mjs idea --title "..."` | 아이디어 기록 | +| `/ac-note` / `/ac-todo` / `/ac-decision` | `node tools/ac.mjs note|todo|decision --title "..."` | 기타 타입 기록 | + +## 결과 중심 기록 (원칙) + +기록에는 **도구 호출 로그를 남기지 않는다**. 도구 사용 과정·출력 전문은 토큰 낭비: + +``` +❌ "Grep으로 검색하고 Read로 3개 읽었더니..." +✅ "JWT race → 전역 mutex 해결. 검증: src/auth/refresh.ts:42" +``` + +- 결론 + `refs`(검증 링크)만 저장. 다음 에이전트는 결론을 쓰거나 refs로 직접 확인 +- 버그는 `repro`에 재현 레시피만 (이것도 과정 로그가 아니라 레시피) + ## References - Concepts from `Coral-Protocol/AgentRadio` (Apache 2.0) and contemporary session collaboration patterns — file-based adaptation. See `docs/radio.md` `docs/sessions.md` `docs/hierarchy.md` `REFERENCES.md`. diff --git a/README.md b/README.md index d9185db..7de6bd1 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,17 @@ cp -r skills/agent-shared-context ~/.codex/skills/ - **`skills/agent-shared-context/SKILL.md`** — OpenCode/Codex generic skill (same, standard frontmatter) - **네이티브 유니버설**: 주력 `Claude Code` `Codex` `OpenCode` 모두 `Node` 네이티브 지원, 추가 언어·의존성 0. 상세는 `docs/skill.md` (또는 skill 파일 자체). +## 명령어 — 스킬 커맨드 1줄 호출 + +| 별칭 | 명령 | +|---|---| +| /ac-export | `node tools/ac.mjs export --session S --task "..." --next "..."` — 세션 내보내기 | +| /ac-import | `node tools/ac.mjs import` — 세션 불러오기 (~280 tok) | +| /ac-history | `node tools/ac.mjs history "query"` — 계층 검색 (0 LLM) | +| /ac-issue / /ac-learning / /ac-idea ... | `node tools/ac.mjs --title "..." [--refs "p1,p2"]` | + +**결과 중심 기록 원칙**: 도구 호출 로그 저장 금지 — 결론 + refs(검증 링크)만. 토큰 낭비 제거. + ## 검증 ```bash diff --git a/agent-context/schema.json b/agent-context/schema.json index 9b11aea..d042d7e 100644 --- a/agent-context/schema.json +++ b/agent-context/schema.json @@ -29,7 +29,13 @@ }, "level": { "type": "string", - "enum": ["post-it", "memo", "diary", "bookshelf", "library"], + "enum": [ + "post-it", + "memo", + "diary", + "bookshelf", + "library" + ], "description": "계층 — post-it(10tok, L1 cache) | memo(50tok, HBM) | diary(200tok, DRAM) | bookshelf(1000tok, SSD) | library(5000tok, cold) — 검색엔진(&AI) 계층, 가벼운 AI가 능동 배정" }, "title": { @@ -161,6 +167,13 @@ } } } + }, + "refs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "결과 중심 기록 — 검증 링크(문서·코드 경로). 도구 실행 로그 대신 결론+링크만 저장" } } } diff --git a/docs/protocol.md b/docs/protocol.md index c99f8b8..8112524 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -67,3 +67,18 @@ Glob pattern="agent-context/diary/2026-08-*.md" - `tools/agent-context-index.mjs --check` 로 drift 감지 (CI에서 필수) - `agent-context/README.md` 동일한 3단계 + +## 결과 중심 기록 (Outcome-based logging) + +기록에는 **도구 호출 로그를 남기지 않는다**. 도구 사용 여부·실행 과정·출력 전문은 토큰 낭비다. 대신: + +``` +❌ 나쁜 예: "Grep으로 검색했고, Read로 3개 파일을 읽었고, 벤치마크를 돌렸더니..." +✅ 좋은 예: "JWT race가 원인 → 전역 mutex로 해결. 검증: src/auth/refresh.ts:42" +``` + +- frontmatter의 `refs` 필드(또는 본문 하단 **검증 링크**)에 확인 경로만 남긴다 +- 다음 에이전트는 결론을 믿거나, 의심되면 refs로 직접 확인한다 — 중간 과정은 필요 없음 +- 단, **재현 가능성**이 필요한 버그는 `repro`(재현 절차)에 최소 정보를 담는다 — 이것도 과정 로그가 아니라 재현 레시피다 + +명령어: `node tools/ac.mjs learning --title "..." --cause C --fix F --lesson L --refs "p1,p2"` 가 이 형식을 강제한다. diff --git a/skills/agent-shared-context/SKILL.md b/skills/agent-shared-context/SKILL.md index c23d968..ccfd5c3 100644 --- a/skills/agent-shared-context/SKILL.md +++ b/skills/agent-shared-context/SKILL.md @@ -85,6 +85,26 @@ node tools/agent-search-lite.mjs "" **서브에이전트 불필요**: 모든 도구는 단일 Bash 호출. 메인 에이전트가 직접 검색, Node 없으면 Grep 폴백(`^level: post-it` → `^priority: [45]`). See `docs/session-continuity.md`. +## Commands (슬래시 별칭 → 단일 Bash 호출) + +| 별칭 | 명령 | 동작 | +|---|---|---| +| `/ac-export` | `node tools/ac.mjs export --session S --task "..." --next "..."` | 세션 내보내기 | +| `/ac-import` | `node tools/ac.mjs import [file]` | 세션 불러오기 | +| `/ac-history` | `node tools/ac.mjs history "query"` | 히스토리 검색 (계층, 0 LLM) | +| `/ac-issue` | `node tools/ac.mjs issue --title "..." [--refs "p1,p2"]` | 이슈 작성 | +| `/ac-learning` | `node tools/ac.mjs learning --title "..." --cause C --fix F --lesson L` | 교훈 기록 | +| `/ac-idea` / `/ac-note` / `/ac-todo` / `/ac-decision` | `node tools/ac.mjs --title "..."` | 기타 기록 | + +## 결과 중심 기록 (원칙) + +도구 호출 로그를 남기지 않는다 — **결론 + `refs`(검증 링크)**만 저장: + +``` +❌ "Grep으로 검색하고 Read로 3개 읽었더니..." +✅ "JWT race → 전역 mutex 해결. 검증: src/auth/refresh.ts:42" +``` + ## References - Concepts from `Coral-Protocol/AgentRadio` (Apache 2.0) and contemporary session collaboration patterns — file-based adaptation. See `docs/radio.md` `docs/sessions.md` `docs/hierarchy.md` `REFERENCES.md`. diff --git a/tools/ac.mjs b/tools/ac.mjs new file mode 100755 index 0000000..84b1f5a --- /dev/null +++ b/tools/ac.mjs @@ -0,0 +1,167 @@ +#!/usr/bin/env node +// Path: tools/ac.mjs +// Single dispatcher for all agent-shared-context commands (skill command layer). +// Slash-style commands in SKILL.md map 1:1 to these subcommands — one Bash call. +// +// Outcome-based logging principle: entries record CONCLUSIONS + pointers, +// not tool-call transcripts. "used tool X" is noise; "result was Y, verify at Z" +// is signal. refs[] holds verification links. +import { spawnSync } from 'node:child_process'; +import { existsSync, readdirSync, writeFileSync, mkdirSync } from 'node:fs'; +import { join } from 'node:path'; + +const TOOLS = new URL('.', import.meta.url).pathname; +const args = process.argv.slice(2); +const cmd = args[0]; +const rest = args.slice(1); + +function run(tool, passthrough) { + const r = spawnSync(process.execPath, [join(TOOLS, tool), ...passthrough], { stdio: 'inherit' }); + process.exit(r.status ?? 0); +} +function getFlag(name) { + const i = rest.indexOf(name); + return i !== -1 ? rest[i + 1] : null; +} +function csvFlag(name) { + const v = getFlag(name); + return v ? v.split(',').map(s => s.trim()).filter(Boolean) : []; +} +function today() { return new Date().toISOString().slice(0, 10); } +function slug(s) { + return String(s).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'entry'; +} +function resolveRoot() { + const cwd = process.cwd(); + if (existsSync(join(cwd, 'agent-context.config.json')) || existsSync(join(cwd, 'agent-context'))) + return join(cwd, 'agent-context'); + return new URL('../agent-context', import.meta.url).pathname; // source-repo fallback +} +function pluralDir(type) { + const map = { issue: 'bugs', bug: 'bugs', learning: 'learnings', idea: 'ideas', note: 'notes', + decision: 'decisions', diary: 'diary', todo: 'todos', 'code-history': 'code-history', + memo: 'notes', 'work-history': 'code-history', 'overall-flow': 'notes' }; + return map[type] || 'notes'; +} +function nextId(type) { + return `${type}-${today().replace(/-/g, '')}-${Math.random().toString(16).slice(2, 10)}`; +} + +// Generic entry creator — outcome-based template with refs field +function createEntry(opts) { + const { type, title, feature = 'global', agent = 'system', status = 'done', + priority = 3, summary, body = '', refs = [] } = opts; + const root = resolveRoot(); + const dir = pluralDir(type); + const targetDir = join(root, dir); + mkdirSync(targetDir, { recursive: true }); + let fname; + if (type === 'decision') { + let n = 1; + try { n = readdirSync(targetDir).filter(f => /^\d{4}-/.test(f)).length + 1; } catch {} + fname = `${String(n).padStart(4, '0')}-${slug(title)}.md`; + } else if (type === 'diary') { + fname = `${today()}.md`; + } else { + fname = `${today()}-${slug(title)}--${agent}.md`; + } + const path = join(targetDir, fname); + if (existsSync(path) && type === 'diary') { + // diary append-only: append a section instead of failing + const prev = readdirSync(targetDir).includes(fname) + ? require('fs').readFileSync(path, 'utf8') : ''; + writeFileSync(path, prev + `\n## ${new Date().toTimeString().slice(0,5)} ${agent} — ${title}\n- ${summary}\n`, 'utf8'); + } else { + const lines = [ + ``, + '---', + `id: ${nextId(type)}`, + `type: ${type}`, + `title: "${String(title).slice(0, 80)}"`, + `tags: [${type}]`, + `feature: ${feature}`, + `level: ""`, + `scope: global`, + `agent: ${agent}`, + `created: ${new Date().toISOString()}`, + `updated: ${new Date().toISOString()}`, + `status: ${status}`, + `priority: ${priority}`, + `summary: "${String(summary || title).slice(0, 180)}"`, + ]; + if (refs.length) { + lines.push('refs:'); + refs.forEach(r => lines.push(` - "${r}"`)); + } + lines.push('---', '', body || '## 결과\n\n(도구 호출 로그 아님 — 결론만 기록. 검증은 refs 링크로)\n'); + writeFileSync(path, lines.join('\n') + '\n', 'utf8'); + } + // regenerate index so level auto-assign runs + spawnSync(process.execPath, [join(TOOLS, 'agent-context-index.mjs')], { stdio: 'inherit' }); + console.log(`created: ${path}`); +} + +const HELP = `Usage: node tools/ac.mjs [args] + +세션 명령: + export --session NAME --task "..." [--done "a;b"] [--next "..."] 세션 내보내기 + import [file] 세션 불러오기 + current 현재 포인터 + +기록 명령 (결과 중심 — 도구 로그 아님): + issue --title "..." [--feature F] [--agent A] [--summary "..."] [--refs a,b] + learning --title "..." [--cause C] [--fix F2] [--lesson L] [동일 옵션] + idea --title "..." [--summary "..."] + note --title "..." [--summary "..."] + todo --title "..." [--status open] + decision --title "..." [--summary "..."] + history "query" [--limit N] [--level L] + +옵션 공통: --feature F --agent A(claude|codex|opencode|human|system) --priority N --refs "p1,p2" + +원칙: 기록에는 '결론 + 확인 링크(refs)'만 남긴다. 도구 실행 과정·출력 전문은 저장하지 않는다.`; + +switch (cmd) { + case 'export': run('agent-handoff.mjs', ['save', ...rest]); break; + case 'import': run('agent-handoff.mjs', ['load', ...rest]); break; + case 'current': run('agent-handoff.mjs', ['current']); break; + case 'history': + case 'search': run('agent-search-lite.mjs', rest); break; + case 'index': run('agent-context-index.mjs', rest); break; + case 'validate':run('agent-context-validate.mjs', rest); break; + case 'issue': + case 'learning': + case 'idea': + case 'note': + case 'todo': + case 'decision': { + const title = getFlag('--title'); + if (!title) { console.error(`${cmd} requires --title "..."`); process.exit(1); } + createEntry({ + type: cmd === 'issue' ? 'issue' : cmd, + title, + feature: getFlag('--feature') || 'global', + agent: getFlag('--agent') || 'system', + status: cmd === 'todo' ? (getFlag('--status') || 'open') + : cmd === 'idea' ? (getFlag('--status') || 'proposed') + : (getFlag('--status') || 'done'), + priority: Number(getFlag('--priority') || (cmd === 'issue' ? 4 : 3)), + summary: getFlag('--summary'), + refs: csvFlag('--refs'), + body: [ + cmd === 'learning' && getFlag('--cause') ? `## 원인\n${getFlag('--cause')}` : null, + cmd === 'learning' && getFlag('--fix') ? `## 해결\n${getFlag('--fix')}` : null, + cmd === 'learning' && getFlag('--lesson') ? `## 교훈\n${getFlag('--lesson')}` : null, + getFlag('--body') ? `## 결과\n${getFlag('--body')}` : null, + refsNote(csvFlag('--refs')), + ].filter(Boolean).join('\n\n'), + }); + break; + } + case '--help': case '-h': default: + console.log(HELP); +} +function refsNote(refs) { + if (!refs.length) return ''; + return `---\n**검증 링크**: ${refs.map(r => `\`${r}\``).join(' · ')}`; +}