@delete101020/aidlc 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/AUTOMATION.md +148 -0
  2. package/README.md +684 -0
  3. package/dist/assets/annotate-artifact.skill.md +121 -0
  4. package/dist/assets/epic-context.skill.md +55 -0
  5. package/dist/bundle.js +49470 -0
  6. package/dist/templates/ainative/agents/native-engineer.md +66 -0
  7. package/dist/templates/ainative/agents/native-operator.md +71 -0
  8. package/dist/templates/ainative/agents/native-originator.md +56 -0
  9. package/dist/templates/ainative/agents/native-product-owner.md +68 -0
  10. package/dist/templates/ainative/agents/native-reviewer.md +65 -0
  11. package/dist/templates/ainative/agents/native-verifier.md +58 -0
  12. package/dist/templates/ainative/artifacts/build-plan.md +56 -0
  13. package/dist/templates/ainative/artifacts/implement.md +55 -0
  14. package/dist/templates/ainative/artifacts/intent.md +50 -0
  15. package/dist/templates/ainative/artifacts/maintain.md +85 -0
  16. package/dist/templates/ainative/artifacts/review.md +68 -0
  17. package/dist/templates/ainative/artifacts/spec.md +69 -0
  18. package/dist/templates/ainative/artifacts/verify.md +55 -0
  19. package/dist/templates/ainative/skills/native-build-plan.md +74 -0
  20. package/dist/templates/ainative/skills/native-implement.md +62 -0
  21. package/dist/templates/ainative/skills/native-intent.md +50 -0
  22. package/dist/templates/ainative/skills/native-maintain.md +94 -0
  23. package/dist/templates/ainative/skills/native-review.md +82 -0
  24. package/dist/templates/ainative/skills/native-spec.md +83 -0
  25. package/dist/templates/ainative/skills/native-verify.md +60 -0
  26. package/dist/templates/sdlc/agents/designer.md +87 -0
  27. package/dist/templates/sdlc/agents/developer.md +178 -0
  28. package/dist/templates/sdlc/agents/example-with-skills.md +59 -0
  29. package/dist/templates/sdlc/agents/orchestrator.md +186 -0
  30. package/dist/templates/sdlc/agents/po.md +115 -0
  31. package/dist/templates/sdlc/agents/qa.md +141 -0
  32. package/dist/templates/sdlc/agents/tech-lead.md +124 -0
  33. package/dist/templates/sdlc/artifacts/design.md +130 -0
  34. package/dist/templates/sdlc/artifacts/execute-test.md +64 -0
  35. package/dist/templates/sdlc/artifacts/implement.backend.md +96 -0
  36. package/dist/templates/sdlc/artifacts/implement.md +70 -0
  37. package/dist/templates/sdlc/artifacts/implement.web-react.md +109 -0
  38. package/dist/templates/sdlc/artifacts/implement.web.md +99 -0
  39. package/dist/templates/sdlc/artifacts/plan.md +97 -0
  40. package/dist/templates/sdlc/artifacts/prototype.md +141 -0
  41. package/dist/templates/sdlc/artifacts/test-cases.md +47 -0
  42. package/dist/templates/sdlc/artifacts/test-plan.md +100 -0
  43. package/dist/templates/sdlc/artifacts/unit-test.md +32 -0
  44. package/dist/templates/sdlc/profiles/agile-lite.yaml +29 -0
  45. package/dist/templates/sdlc/profiles/hybrid.yaml +43 -0
  46. package/dist/templates/sdlc/profiles/iso-ieee.yaml +67 -0
  47. package/dist/templates/sdlc/profiles/none.yaml +21 -0
  48. package/dist/templates/sdlc/skills/advance-epic.md +77 -0
  49. package/dist/templates/sdlc/skills/analyze-requirements.md +279 -0
  50. package/dist/templates/sdlc/skills/discovery-gate.md +118 -0
  51. package/dist/templates/sdlc/skills/execute-test.md +74 -0
  52. package/dist/templates/sdlc/skills/generate-test-cases.md +77 -0
  53. package/dist/templates/sdlc/skills/implement.md +74 -0
  54. package/dist/templates/sdlc/skills/prd.md +114 -0
  55. package/dist/templates/sdlc/skills/prototype.md +128 -0
  56. package/dist/templates/sdlc/skills/tech-design.md +134 -0
  57. package/dist/templates/sdlc/skills/test-plan.md +115 -0
  58. package/dist/templates/sdlc/skills/test-report.md +38 -0
  59. package/dist/templates/sdlc/skills/unit-test.md +43 -0
  60. package/dist/templates/sdlc/validators/ci.mjs +49 -0
  61. package/dist/templates/sdlc/validators/traceability.mjs +244 -0
  62. package/dist/templates/speckit/agents/analyst.md +42 -0
  63. package/dist/templates/speckit/agents/speckit-developer.md +36 -0
  64. package/dist/templates/speckit/agents/speckit-qa.md +38 -0
  65. package/dist/templates/speckit/agents/speckit-tech-lead.md +40 -0
  66. package/dist/templates/speckit/artifacts/analyze.md +48 -0
  67. package/dist/templates/speckit/artifacts/clarify.md +64 -0
  68. package/dist/templates/speckit/artifacts/plan.md +57 -0
  69. package/dist/templates/speckit/artifacts/specify.md +64 -0
  70. package/dist/templates/speckit/artifacts/tasks.md +33 -0
  71. package/dist/templates/speckit/skills/analyze.md +50 -0
  72. package/dist/templates/speckit/skills/clarify.md +31 -0
  73. package/dist/templates/speckit/skills/plan.md +56 -0
  74. package/dist/templates/speckit/skills/specify.md +52 -0
  75. package/dist/templates/speckit/skills/speckit-implement.md +33 -0
  76. package/dist/templates/speckit/skills/tasks.md +38 -0
  77. package/dist/tools/epic-memory-hook.mjs +78 -0
  78. package/dist/tools/epic-memory.mjs +130 -0
  79. package/dist/tools/md-to-html.mjs +478 -0
  80. package/dist/tools/vendor/marked.esm.mjs +2432 -0
  81. package/dist/vendor/annotron/LICENSE +21 -0
  82. package/dist/vendor/annotron/README.md +213 -0
  83. package/dist/vendor/annotron/bin/annotron +467 -0
  84. package/dist/vendor/annotron/commands/annotron.md +11 -0
  85. package/dist/vendor/annotron/hooks/annotron-hook.sh +38 -0
  86. package/dist/vendor/annotron/hooks/hooks.json +50 -0
  87. package/dist/vendor/annotron/package.json +72 -0
  88. package/dist/vendor/annotron/skills/annotron-review/SKILL.md +86 -0
  89. package/dist/vendor/annotron/src/chrome.html +2029 -0
  90. package/dist/vendor/annotron/src/mdRender.js +208 -0
  91. package/dist/vendor/annotron/src/sdk.js +1042 -0
  92. package/dist/vendor/annotron/src/server.js +780 -0
  93. package/package.json +60 -0
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env node
2
+ import http from 'http';
3
+ import https from 'https';
4
+ import path from 'path';
5
+ import fs from 'fs';
6
+ import { fileURLToPath } from 'url';
7
+ import { spawn } from 'child_process';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const PORT = parseInt(process.env.ANNOTRON_PORT || '7321', 10);
11
+ const HOST = process.env.ANNOTRON_HOST || '127.0.0.1';
12
+ const BASE = `http://${HOST}:${PORT}`;
13
+
14
+ // --- Helpers ---
15
+ function apiFetch(path, opts = {}) {
16
+ return new Promise((resolve, reject) => {
17
+ const url = new URL(path, BASE);
18
+ const reqOpts = {
19
+ hostname: url.hostname,
20
+ port: url.port,
21
+ path: url.pathname + url.search,
22
+ method: opts.method || 'GET',
23
+ headers: opts.headers || {},
24
+ };
25
+ const req = http.request(reqOpts, (res) => {
26
+ let data = '';
27
+ res.on('data', c => data += c);
28
+ res.on('end', () => resolve({ status: res.statusCode, body: data }));
29
+ });
30
+ req.on('error', reject);
31
+ if (opts.body) req.write(opts.body);
32
+ req.end();
33
+ });
34
+ }
35
+
36
+ async function isServerRunning() {
37
+ try {
38
+ const r = await apiFetch('/health');
39
+ return r.status === 200;
40
+ } catch { return false; }
41
+ }
42
+
43
+ async function ensureServer() {
44
+ if (await isServerRunning()) return;
45
+ const serverPath = path.join(__dirname, '..', 'src', 'server.js');
46
+ const child = spawn(process.execPath, [serverPath], {
47
+ stdio: 'ignore',
48
+ detached: true,
49
+ env: { ...process.env },
50
+ });
51
+ child.unref();
52
+
53
+ // Wait up to 3s for it to start
54
+ for (let i = 0; i < 15; i++) {
55
+ await sleep(200);
56
+ if (await isServerRunning()) return;
57
+ }
58
+ throw new Error('Server failed to start');
59
+ }
60
+
61
+ function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
62
+
63
+ async function postJSON(path, body) {
64
+ return apiFetch(path, {
65
+ method: 'POST',
66
+ headers: { 'Content-Type': 'application/json' },
67
+ body: JSON.stringify(body),
68
+ });
69
+ }
70
+
71
+ function openBrowser(url) {
72
+ const opts = { stdio: 'ignore', detached: true };
73
+ if (process.platform === 'win32') {
74
+ // `start` is a cmd.exe builtin, NOT an executable — `spawn('start', …)`
75
+ // throws ENOENT and the browser never opens (issue #83, Windows). Go
76
+ // through cmd.exe. The empty "" is start's window-title argument, so the
77
+ // URL isn't swallowed as the title.
78
+ spawn('cmd', ['/c', 'start', '', url], opts).unref();
79
+ } else {
80
+ const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
81
+ spawn(cmd, [url], opts).unref();
82
+ }
83
+ }
84
+
85
+ // --- Commands ---
86
+ async function cmdOpen(file) {
87
+ if (!file) { console.error('Usage: annotron <file.html|file.md>'); process.exit(1); }
88
+ const abs = path.resolve(file);
89
+ if (!fs.existsSync(abs)) { console.error(`File not found: ${abs}`); process.exit(1); }
90
+ if (!abs.endsWith('.html') && !/\.md$/i.test(abs)) { console.error('File must be .html or .md'); process.exit(1); }
91
+
92
+ await ensureServer();
93
+
94
+ const r = await postJSON('/session', { file: abs });
95
+ if (r.status !== 200) {
96
+ const err = JSON.parse(r.body);
97
+ console.error('Session error:', err.error);
98
+ process.exit(1);
99
+ }
100
+
101
+ const editorUrl = `${BASE}/?file=${encodeURIComponent(abs)}`;
102
+ console.log('Editor URL:', editorUrl);
103
+ openBrowser(editorUrl);
104
+
105
+ // `annotron <file> --agent` also starts the auto-apply agent loop in the
106
+ // foreground, so feedback is handled without a separate command.
107
+ if (process.argv.includes('--agent')) {
108
+ console.log('');
109
+ await cmdAgent(abs);
110
+ } else {
111
+ console.log('Agent: annotron agent', abs, ' (auto-apply feedback with Claude)');
112
+ }
113
+ }
114
+
115
+ async function cmdPoll(file, reply, annotationId) {
116
+ if (!file) { console.error('Usage: annotron poll <file.html> [--reply "msg"]'); process.exit(1); }
117
+ const abs = path.resolve(file);
118
+
119
+ if (reply) {
120
+ const r = await postJSON('/agent-reply', { file: abs, message: reply, annotationId: annotationId || null });
121
+ if (r.status !== 200) { console.error('Reply failed:', r.body); }
122
+ }
123
+
124
+ // Long-poll loop
125
+ let result = null;
126
+ while (!result) {
127
+ let r;
128
+ try {
129
+ r = await apiFetch('/poll?file=' + encodeURIComponent(abs));
130
+ } catch (e) {
131
+ console.error('Poll error:', e.message);
132
+ process.exit(1);
133
+ }
134
+ if (r.status !== 200) { console.error('Poll error:', r.body); process.exit(1); }
135
+ const body = JSON.parse(r.body);
136
+ if (body.finalized) {
137
+ console.log(JSON.stringify({ finalized: true }));
138
+ result = body;
139
+ } else if (body.feedback) {
140
+ console.log(JSON.stringify(body.feedback, null, 2));
141
+ result = body;
142
+ }
143
+ // else keep-alive empty response — loop
144
+ }
145
+ }
146
+
147
+ async function cmdProgress(file, step) {
148
+ if (!file) { console.error('Usage: annotron progress <file.html> "step description"'); process.exit(1); }
149
+ const abs = path.resolve(file);
150
+ try {
151
+ const r = await postJSON('/agent-progress', { file: abs, step: step || '' });
152
+ if (r.status !== 200) { console.error('Progress failed:', r.body); process.exit(1); }
153
+ } catch (e) { console.error('Progress error:', e.message); process.exit(1); }
154
+ }
155
+
156
+ async function cmdCheck(file) {
157
+ if (!file) { console.error('Usage: annotron check <file.html>'); process.exit(1); }
158
+ const abs = path.resolve(file);
159
+ try {
160
+ const r = await apiFetch('/cancelled?file=' + encodeURIComponent(abs));
161
+ if (r.status !== 200) { console.log(JSON.stringify({ cancelled: false })); return; }
162
+ process.stdout.write(r.body.trim() + '\n');
163
+ } catch { console.log(JSON.stringify({ cancelled: false })); }
164
+ }
165
+
166
+ async function cmdStop() {
167
+ try {
168
+ await postJSON('/stop', {});
169
+ console.log('Server stopped.');
170
+ } catch { console.log('Server not running.'); }
171
+ }
172
+
173
+ // --- Agent loop: poll for feedback and auto-apply it with Claude Code ---------
174
+ // `annotron agent <file>` sits in the feedback loop so the reviewer never has to
175
+ // wire anything: it waits for feedback, runs `claude -p` to edit the source
176
+ // file, and replies — then loops. This is what turns "Send feedback" in the
177
+ // browser into applied edits automatically.
178
+
179
+ // Build a clean environment for spawning `claude`.
180
+ //
181
+ // The VS Code extension launches this agent with VS Code's Electron acting as
182
+ // Node (ELECTRON_RUN_AS_NODE=1) and injects VSCODE_*/ELECTRON_* vars plus a
183
+ // NODE_OPTIONS that --require's VS Code internals. If those leak into `claude`,
184
+ // the Node subprocesses it spawns (shell for Bash, hooks, ripgrep) try to load
185
+ // VS Code's hooks and hang — the run stalls until our timeout fires. Strip them
186
+ // so claude sees a normal shell environment. Also augment PATH so `claude`
187
+ // resolves even when launched from a GUI (Dock).
188
+ function agentEnv() {
189
+ const home = process.env.HOME || '';
190
+ const extra = ['/opt/homebrew/bin', '/usr/local/bin', `${home}/.local/bin`, `${home}/.claude/local`]
191
+ .filter(Boolean).join(':');
192
+ const env = { ...process.env };
193
+ delete env.ELECTRON_RUN_AS_NODE;
194
+ delete env.NODE_OPTIONS;
195
+ for (const k of Object.keys(env)) {
196
+ if (k.startsWith('ELECTRON_') || k.startsWith('VSCODE_')) delete env[k];
197
+ }
198
+ // A stale/invalid ANTHROPIC_API_KEY exported in the user's shell profile makes
199
+ // the spawned `claude` authenticate with a bad key → 401 → endless api_retry →
200
+ // our timeout, even though the user's interactive `claude` works fine via its
201
+ // subscription login. Drop the key so the applier uses the same logged-in
202
+ // (OAuth) auth as the user's CLI. Set ANNOTRON_KEEP_API_KEY=1 to keep it if
203
+ // you deliberately authenticate with an API key.
204
+ if (!process.env.ANNOTRON_KEEP_API_KEY) delete env.ANTHROPIC_API_KEY;
205
+ env.PATH = `${process.env.PATH || ''}:${extra}`;
206
+ return env;
207
+ }
208
+
209
+ function buildAgentPrompt(abs, items, message) {
210
+ const list = (items || []).map((it, i) => {
211
+ const where = it.text ? `the text "${it.text}"` : (it.label ? `the element ${it.label}` : `(${it.kind})`);
212
+ return `${i + 1}. On ${where}: ${it.note || '(no note)'}`;
213
+ }).join('\n');
214
+ return [
215
+ `Apply human review feedback to this source file: ${abs}`,
216
+ `Edit the file directly — it is the source of truth (Markdown or HTML). Make ONLY the requested changes:`,
217
+ list || '(see the reviewer note below)',
218
+ message ? `\nReviewer note:\n${message}` : '',
219
+ `\nAfter editing, reply with ONE short sentence describing what you changed.`,
220
+ ].filter(Boolean).join('\n');
221
+ }
222
+
223
+ // Turn one stream-json event into a short, human-readable activity line for the
224
+ // browser's live log — or null for events not worth showing.
225
+ function formatStep(ev) {
226
+ try {
227
+ if (ev.type !== 'assistant') return null;
228
+ for (const b of (ev.message?.content || [])) {
229
+ if (b.type === 'tool_use') {
230
+ const inp = b.input || {};
231
+ const f = inp.file_path ? inp.file_path.split('/').pop() : '';
232
+ switch (b.name) {
233
+ case 'Read': return f ? `Reading ${f}` : 'Reading file';
234
+ case 'Edit': return f ? `Editing ${f}` : 'Editing file';
235
+ case 'Write': return f ? `Writing ${f}` : 'Writing file';
236
+ case 'Grep': return `Searching “${inp.pattern || ''}”`;
237
+ case 'Glob': return `Finding ${inp.pattern || 'files'}`;
238
+ case 'ToolSearch': return null; // internal tool-loading noise
239
+ default: return b.name;
240
+ }
241
+ }
242
+ if (b.type === 'text' && b.text && b.text.trim()) {
243
+ const t = b.text.trim().replace(/\s+/g, ' ');
244
+ return t.length > 90 ? t.slice(0, 90) + '…' : t;
245
+ }
246
+ }
247
+ } catch {}
248
+ return null;
249
+ }
250
+
251
+ function runClaude(prompt, cwd, abs) {
252
+ return new Promise((resolve, reject) => {
253
+ // stdin must be closed ('ignore'): in -p mode `claude` waits on stdin and
254
+ // otherwise hangs ("no stdin data received in 3s") since we pass the prompt
255
+ // as an argument, not via a pipe.
256
+ //
257
+ // Keep the applier lean and fast:
258
+ // --strict-mcp-config skip the project's MCP servers. Applying a small
259
+ // text/HTML edit needs none of them, and connecting to
260
+ // unauthenticated servers (e.g. claude.ai connectors)
261
+ // can stall the run for minutes.
262
+ // --disallowedTools HARD-block Task/Agent so the run never delegates the
263
+ // edit to a subagent. A user CLAUDE.md that routes
264
+ // "routine" work to a subagent would otherwise spawn a
265
+ // nested Claude session per comment (minutes, not
266
+ // seconds). --allowedTools alone does NOT prevent this
267
+ // — it only pre-approves tools, it doesn't remove them.
268
+ // --model sonnet applying review feedback is mechanical editing —
269
+ // Sonnet is accurate here and faster than Opus.
270
+ // Override with ANNOTRON_AGENT_MODEL.
271
+ // --output-format stream-json so we can surface a live activity log
272
+ // (what file it's reading/editing) in the browser.
273
+ const model = process.env.ANNOTRON_AGENT_MODEL || 'sonnet';
274
+ const args = [
275
+ '-p', prompt,
276
+ '--permission-mode', 'acceptEdits',
277
+ '--strict-mcp-config',
278
+ '--allowedTools', 'Read', 'Edit', 'Write', 'Grep', 'Glob',
279
+ '--disallowedTools', 'Task', 'Agent',
280
+ '--model', model,
281
+ '--output-format', 'stream-json', '--verbose',
282
+ ];
283
+ // ── Debug log ─────────────────────────────────────────────────────────
284
+ // Everything about this run is written to <file>.agent.log (override with
285
+ // ANNOTRON_AGENT_LOG) AND echoed to the terminal, so a hang in the real
286
+ // extension environment can be diagnosed: exact command, whether VS Code's
287
+ // env vars were stripped, every stream event, stderr, and — on timeout —
288
+ // the last event seen (i.e. where it stalled).
289
+ const t0 = Date.now();
290
+ const logPath = process.env.ANNOTRON_AGENT_LOG || (abs ? abs + '.agent.log' : null);
291
+ const secs = () => ((Date.now() - t0) / 1000).toFixed(1) + 's';
292
+ const log = (msg) => {
293
+ const line = `[+${secs()}] ${msg}`;
294
+ console.log(' ' + line);
295
+ if (logPath) { try { fs.appendFileSync(logPath, line + '\n'); } catch {} }
296
+ };
297
+ const env = agentEnv();
298
+ if (logPath) { try { fs.appendFileSync(logPath, `\n===== run @ ${new Date().toISOString()} =====\n`); } catch {} }
299
+ log(`cwd=${cwd} model=${model} timeout=${(Number(process.env.ANNOTRON_AGENT_TIMEOUT_MS) || 180000) / 1000}s`);
300
+ log(`claude flags: ${args.filter(a => a !== prompt).join(' ')}`);
301
+ log(`env: parent ELECTRON_RUN_AS_NODE=${process.env.ELECTRON_RUN_AS_NODE || '(unset)'} → child=${env.ELECTRON_RUN_AS_NODE || '(stripped)'} | child NODE_OPTIONS=${env.NODE_OPTIONS || '(stripped)'} | VSCODE_* left=${Object.keys(env).filter(k => k.startsWith('VSCODE_')).length}`);
302
+ // Resolve which `claude` binary PATH picks — a VS Code-spawned process can
303
+ // have a different PATH than a login shell and pick a different claude.
304
+ let claudeBin = '(not found on PATH)';
305
+ for (const dir of (env.PATH || '').split(':')) {
306
+ if (!dir) continue;
307
+ const cand = path.join(dir, 'claude');
308
+ try { fs.accessSync(cand, fs.constants.X_OK); claudeBin = cand; break; } catch {}
309
+ }
310
+ log(`claude binary: ${claudeBin}`);
311
+ if (logPath) { try { fs.appendFileSync(logPath, `PATH=${env.PATH}\n`); } catch {} }
312
+
313
+ const child = spawn('claude', args, { cwd, env, stdio: ['ignore', 'pipe', 'pipe'] });
314
+ log(`spawned pid=${child.pid}`);
315
+ let err = '', buf = '', lastText = '', resultText = null, done = false, events = 0;
316
+ let lastDesc = '(no output yet)', lastAt = Date.now();
317
+ // Hard timeout so a wedged run reports back instead of leaving the UI stuck
318
+ // on "working" forever. Override with ANNOTRON_AGENT_TIMEOUT_MS.
319
+ const timeoutMs = Number(process.env.ANNOTRON_AGENT_TIMEOUT_MS) || 180000;
320
+ // Stall heartbeat: if nothing arrives for 15s, say so (and what the last
321
+ // event was), so a silent hang is visible while it's happening.
322
+ const heartbeat = setInterval(() => {
323
+ const idle = ((Date.now() - lastAt) / 1000).toFixed(0);
324
+ if (idle >= 12) log(`… still waiting (${idle}s since last event: ${lastDesc})`);
325
+ }, 15000);
326
+ const settle = (fn, arg) => { if (done) return; done = true; clearTimeout(timer); clearInterval(heartbeat); fn(arg); };
327
+ const timer = setTimeout(() => {
328
+ log(`TIMEOUT after ${Math.round(timeoutMs / 1000)}s — ${events} events; last was "${lastDesc}" ${((Date.now() - lastAt) / 1000).toFixed(0)}s ago. Killing.`);
329
+ if (err.trim()) log('stderr so far: ' + err.trim().split('\n').slice(-5).join(' | '));
330
+ try { child.kill('SIGKILL'); } catch {}
331
+ settle(reject, new Error(`claude timed out after ${Math.round(timeoutMs / 1000)}s`));
332
+ }, timeoutMs);
333
+ const postStep = (step) => {
334
+ if (step && abs) postJSON('/agent-progress', { file: abs, step }).catch(() => {});
335
+ };
336
+ const handleLine = (line) => {
337
+ line = line.trim();
338
+ if (!line) return;
339
+ let ev; try { ev = JSON.parse(line); } catch { log('non-JSON: ' + line.slice(0, 120)); return; }
340
+ events++; lastAt = Date.now();
341
+ if (ev.type === 'result' && typeof ev.result === 'string') resultText = ev.result;
342
+ if (ev.type === 'assistant') {
343
+ for (const b of (ev.message?.content || [])) {
344
+ if (b.type === 'text' && b.text && b.text.trim()) lastText = b.text.trim();
345
+ }
346
+ }
347
+ const step = formatStep(ev);
348
+ lastDesc = step || (ev.subtype ? `${ev.type}/${ev.subtype}` : ev.type);
349
+ log(`event: ${lastDesc}`);
350
+ // API retries mean the request itself is failing (network/auth/proxy) —
351
+ // dump the raw event so we can see the actual reason (status/error).
352
+ if (ev.type === 'system' && ev.subtype && ev.subtype !== 'init' && ev.subtype.indexOf('task') !== 0) {
353
+ log(' raw: ' + JSON.stringify(ev).slice(0, 400));
354
+ }
355
+ postStep(step);
356
+ };
357
+ child.stdout.on('data', (c) => {
358
+ buf += c;
359
+ let i;
360
+ while ((i = buf.indexOf('\n')) >= 0) { handleLine(buf.slice(0, i)); buf = buf.slice(i + 1); }
361
+ });
362
+ child.stderr.on('data', (c) => { err += c; log('stderr: ' + String(c).trim().slice(0, 200)); });
363
+ child.on('error', e => { log('spawn error: ' + e.message); settle(reject, e); });
364
+ child.on('close', code => {
365
+ if (buf.trim()) handleLine(buf);
366
+ const summary = (resultText || lastText || '').trim();
367
+ log(`closed code=${code} in ${secs()}, ${events} events`);
368
+ if (code === 0) settle(resolve, summary || 'Applied your feedback.');
369
+ else settle(reject, new Error((err.trim() || summary || `claude exited ${code}`).split('\n').slice(-3).join(' ')));
370
+ });
371
+ });
372
+ }
373
+
374
+ // Log a line to the terminal AND to the same <file>.agent.log that runClaude
375
+ // writes, so the whole agent lifecycle (feedback received, Done, stopping) is
376
+ // visible in one place — not just the claude sub-run.
377
+ function agentLog(abs, msg) {
378
+ const line = `[${new Date().toISOString()}] ${msg}`;
379
+ console.log(line);
380
+ const logPath = process.env.ANNOTRON_AGENT_LOG || (abs ? abs + '.agent.log' : null);
381
+ if (logPath) { try { fs.appendFileSync(logPath, line + '\n'); } catch {} }
382
+ }
383
+
384
+ async function cmdAgent(file) {
385
+ if (!file) { console.error('Usage: annotron agent <file>'); process.exit(1); }
386
+ const abs = path.resolve(file);
387
+ if (!fs.existsSync(abs)) { console.error(`File not found: ${abs}`); process.exit(1); }
388
+ await ensureServer();
389
+ const s = await postJSON('/session', { file: abs });
390
+ if (s.status !== 200) { console.error('Session error:', s.body); process.exit(1); }
391
+ agentLog(abs, `annotron agent • watching ${abs}`);
392
+ console.log('Waiting for feedback from the browser… (Ctrl+C to stop)\n');
393
+ for (;;) {
394
+ let body;
395
+ try {
396
+ const r = await apiFetch('/poll?file=' + encodeURIComponent(abs));
397
+ if (r.status !== 200) { await sleep(1500); continue; }
398
+ body = JSON.parse(r.body);
399
+ } catch { await sleep(1500); continue; }
400
+ if (body.finalized) { agentLog(abs, '■ Done received from browser — review finished, agent stopping.'); break; }
401
+ if (!body.feedback) continue; // keep-alive tick
402
+ const items = body.feedback.items || [];
403
+ const message = (body.feedback.message || '').trim();
404
+ agentLog(abs, `▶ Feedback: ${items.length} comment(s)${message ? ' + message' : ''} — applying with claude…`);
405
+ let summary = '', ok = true;
406
+ try { summary = await runClaude(buildAgentPrompt(abs, items, message), path.dirname(abs), abs); }
407
+ catch (e) { summary = `Could not apply automatically: ${e.message}`; ok = false; }
408
+ const short = summary.split('\n').filter(Boolean).slice(-1)[0] || 'Applied your feedback.';
409
+ for (const it of items) {
410
+ // `failed` tells the server NOT to auto-resolve a comment we couldn't apply.
411
+ if (it.id) await postJSON('/agent-reply', {
412
+ file: abs, annotationId: it.id,
413
+ message: ok ? `Applied — ${it.note || it.label || 'done'}.` : short,
414
+ failed: !ok,
415
+ }).catch(() => {});
416
+ }
417
+ await postJSON('/agent-reply', { file: abs, message: short }).catch(() => {});
418
+ agentLog(abs, `✓ ${short}\n`);
419
+ }
420
+ }
421
+
422
+ function cmdHelp() {
423
+ console.log(`
424
+ annotron — local review editor for agent-generated HTML artifacts
425
+
426
+ Usage:
427
+ annotron <file.html|file.md> Open the editor in browser
428
+ annotron <file> --agent Open + auto-apply feedback with Claude
429
+ annotron agent <file> Run the auto-apply agent loop (poll → claude → reply)
430
+ annotron poll <file> Wait for feedback (low-level, run by an agent)
431
+ annotron poll <file> --reply "…" Post a reply then wait for feedback
432
+ annotron progress <file> "step" Post a live progress step (shown in the browser)
433
+ annotron check <file> Print {"cancelled":true|false} for the session
434
+ annotron stop Shut down the background server
435
+ annotron help Show this help
436
+
437
+ Environment:
438
+ ANNOTRON_PORT Server port (default: 7321)
439
+ ANNOTRON_HOST Server bind host (default: 127.0.0.1)
440
+ `);
441
+ }
442
+
443
+ // --- Main ---
444
+ const args = process.argv.slice(2);
445
+ const cmd = args[0];
446
+
447
+ if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
448
+ cmdHelp();
449
+ } else if (cmd === 'stop') {
450
+ await cmdStop();
451
+ } else if (cmd === 'progress') {
452
+ await cmdProgress(args[1], args[2]);
453
+ } else if (cmd === 'check') {
454
+ await cmdCheck(args[1]);
455
+ } else if (cmd === 'poll') {
456
+ const file = args[1];
457
+ const replyIdx = args.indexOf('--reply');
458
+ const reply = replyIdx !== -1 ? args[replyIdx + 1] : null;
459
+ const annIdx = args.indexOf('--annotation-id');
460
+ const annotationId = annIdx !== -1 ? args[annIdx + 1] : null;
461
+ await cmdPoll(file, reply, annotationId);
462
+ } else if (cmd === 'agent') {
463
+ await cmdAgent(args[1]);
464
+ } else {
465
+ // annotron <file.html|file.md> [--agent]
466
+ await cmdOpen(cmd);
467
+ }
@@ -0,0 +1,11 @@
1
+ ---
2
+ description: Open the current HTML artifact in the annotron review editor and start the feedback loop.
3
+ ---
4
+
5
+ Open the most recent HTML artifact ($ARGUMENTS or the last .html file written this session)
6
+ in the annotron review editor, then run the full feedback loop:
7
+
8
+ 1. `annotron <artifact.html>` — open editor in browser
9
+ 2. `annotron poll <artifact.html>` — wait for user feedback
10
+ 3. Apply feedback, then `annotron poll <artifact.html> --reply "..."` to notify the user
11
+ 4. Repeat until poll returns `finalized: true`
@@ -0,0 +1,38 @@
1
+ #!/bin/sh
2
+ # annotron bridge hook. Forwards a Claude Code hook event to the local annotron
3
+ # server so the browser can mirror activity, show status, and (optionally) gate
4
+ # tool permissions.
5
+ #
6
+ # Usage (from hooks.json):
7
+ # annotron-hook.sh gate /hook/pretool # PreToolUse — may return a decision
8
+ # annotron-hook.sh fire /hook/posttool # PostToolUse — fire-and-forget
9
+ # annotron-hook.sh fire /hook/notify # Notification
10
+ # annotron-hook.sh fire /hook/stop # Stop
11
+ #
12
+ # Design goals: zero cost when annotron isn't running (curl fails fast → allow),
13
+ # never blocks annotron's own CLI, no hard dependency on jq (server parses JSON).
14
+
15
+ mode="$1"
16
+ endpoint="$2"
17
+ input=$(cat)
18
+
19
+ host="${ANNOTRON_HOST:-127.0.0.1}"
20
+ port="${ANNOTRON_PORT:-7321}"
21
+ url="http://$host:$port$endpoint"
22
+
23
+ if [ "$mode" = "gate" ]; then
24
+ # (annotron's own CLI calls are exempted server-side, after a proper JSON
25
+ # parse — matching raw stdin here is unreliable with escaped quotes/paths.)
26
+ # Long timeout: the server may hold the request while the user decides in the
27
+ # browser. It resolves to "ask" (terminal fallback) well before this ceiling.
28
+ resp=$(printf '%s' "$input" | curl -s --max-time 185 \
29
+ -H 'Content-Type: application/json' --data-binary @- "$url" 2>/dev/null) || exit 0
30
+ [ -z "$resp" ] && exit 0 # empty = defer to Claude Code's normal flow
31
+ printf '%s' "$resp" # echo the decision JSON back to Claude Code
32
+ exit 0
33
+ fi
34
+
35
+ # fire-and-forget: short timeout, never block the tool; ignore the response.
36
+ printf '%s' "$input" | curl -s --max-time 3 \
37
+ -H 'Content-Type: application/json' --data-binary @- "$url" >/dev/null 2>&1 || true
38
+ exit 0
@@ -0,0 +1,50 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit|Read|Bash|Grep|Glob|WebFetch|WebSearch|Task",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/annotron-hook.sh gate /hook/pretool",
10
+ "timeout": 190
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PostToolUse": [
16
+ {
17
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit|Read|Bash|Grep|Glob|WebFetch|WebSearch|Task",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/annotron-hook.sh fire /hook/posttool",
22
+ "timeout": 5
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "Notification": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/annotron-hook.sh fire /hook/notify",
33
+ "timeout": 5
34
+ }
35
+ ]
36
+ }
37
+ ],
38
+ "Stop": [
39
+ {
40
+ "hooks": [
41
+ {
42
+ "type": "command",
43
+ "command": "\"${CLAUDE_PLUGIN_ROOT}\"/hooks/annotron-hook.sh fire /hook/stop",
44
+ "timeout": 5
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "annotron",
3
+ "version": "1.0.0",
4
+ "description": "Human-in-the-loop review editor for AI agents. Point-and-click annotate HTML/Markdown artifacts, send structured feedback to Claude Code, watch the integrated agent loop auto-apply changes live via loop engineering, browse outline navigation for docs, then finalize & download. Zero setup — just `--agent` and annotate.",
5
+ "type": "module",
6
+ "bin": {
7
+ "annotron": "bin/annotron"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "skills/",
13
+ "commands/",
14
+ "hooks/"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/hueanmy/annotron.git"
19
+ },
20
+ "publishConfig": {
21
+ "registry": "https://registry.npmjs.org/"
22
+ },
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "keywords": [
27
+ "claude",
28
+ "claude-code",
29
+ "anthropic",
30
+ "claude-plugin",
31
+ "html",
32
+ "artifacts",
33
+ "markdown",
34
+ "annotation",
35
+ "code-review",
36
+ "ai-agents",
37
+ "agentic",
38
+ "developer-tools",
39
+ "cli",
40
+ "feedback",
41
+ "llm",
42
+ "agent-loop",
43
+ "agentic-loop",
44
+ "feedback-loop",
45
+ "loop-engineering",
46
+ "auto-apply",
47
+ "auto-feedback",
48
+ "continuous-feedback",
49
+ "human-in-the-loop",
50
+ "in-the-loop",
51
+ "agent-feedback",
52
+ "outline-navigation",
53
+ "architecture-docs",
54
+ "tech-design",
55
+ "mermaid",
56
+ "diagram",
57
+ "observability",
58
+ "agent-observability",
59
+ "activity-stream",
60
+ "context-engineering",
61
+ "prompt-engineering"
62
+ ],
63
+ "author": "Meii",
64
+ "license": "MIT",
65
+ "dependencies": {
66
+ "markdown-it": "^14.3.0",
67
+ "merslim": "^0.2.3"
68
+ },
69
+ "devDependencies": {
70
+ "playwright": "^1.61.1"
71
+ }
72
+ }