knoxis-helper 1.6.4 → 1.7.2

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.
@@ -12,12 +12,12 @@
12
12
  // "what methodology version is baked into it." Both ride together in every
13
13
  // session record so the portal can render them.
14
14
  module.exports = {
15
- bundle: '1.0.0',
15
+ bundle: '1.1.0',
16
16
  documents: {
17
17
  overview: 1,
18
- kickoff: 7,
19
- resume: 3,
20
- sessionEnd: 3,
18
+ kickoff: 8,
19
+ resume: 4,
20
+ sessionEnd: 4,
21
21
  recovery: 3,
22
22
  codingRuleset: 3,
23
23
  marketplaceMcp: 3,
@@ -36,6 +36,7 @@ const os = require('os');
36
36
  const { SessionRecorder } = require('./session-recorder');
37
37
  const { scaffoldStateLayout } = require('./state-scaffold');
38
38
  const { syncSessionToPortal } = require('./portal-sync');
39
+ const kitTemplates = require('./templates');
39
40
 
40
41
  // === CONFIG ===
41
42
  const CONFIG_PATH = path.join(os.homedir(), '.knoxis', 'config.json');
@@ -354,10 +355,89 @@ async function finalizeSession(recorder) {
354
355
  }
355
356
  }
356
357
 
358
+ // === KIT MODE (kickoff / resume) ===
359
+ // When the local agent routes a kit mode here, it sets KNOXIS_KIT_MODE so we
360
+ // load the matching kit template (the same module pair-program.js uses) and
361
+ // run it as a single Claude turn via runClaudeTurn — i.e. through `claude -p
362
+ // --session-id`, which has the file-writing tool access the kit prompts need.
363
+ // The 4-phase Groq flow is bypassed entirely; kit prompts are self-contained
364
+ // frameworks with their own state machine.
365
+ async function runKitMode(kitMode, task, identity, scaffoldResult) {
366
+ const archetype = process.env.KNOXIS_KIT_ARCHETYPE || null;
367
+ const pattern = process.env.KNOXIS_KIT_PATTERN || null;
368
+ let tpl;
369
+ try {
370
+ tpl = kitTemplates.getTemplate(kitMode, {
371
+ taskDescription: task || null,
372
+ archetype,
373
+ pattern,
374
+ productSlug: identity.productSlug,
375
+ projectSlug: identity.projectSlug,
376
+ workspace: identity.workspace
377
+ });
378
+ } catch (e) {
379
+ console.error('Kit template error: ' + e.message);
380
+ process.exit(1);
381
+ }
382
+
383
+ const recorder = new SessionRecorder(task || `[${kitMode} session]`, identity.workspace, 'Claude Code (kit)', {
384
+ mode: kitMode,
385
+ archetype,
386
+ productSlug: identity.productSlug,
387
+ projectSlug: identity.projectSlug,
388
+ engineerId: identity.engineerId,
389
+ userId: identity.userId,
390
+ workspaceId: identity.workspaceId,
391
+ taskIds: identity.taskIds
392
+ });
393
+
394
+ console.log('');
395
+ console.log('╔══════════════════════════════════════════════════════════════╗');
396
+ console.log('║ KNOXIS KIT MODE: ' + kitMode.toUpperCase().padEnd(36) + '║');
397
+ console.log('╚══════════════════════════════════════════════════════════════╝');
398
+ console.log('');
399
+ console.log(' Mode: ' + kitMode + (archetype ? ' (archetype: ' + archetype + ')' : ''));
400
+ console.log(' Workspace: ' + identity.workspace);
401
+ console.log(' Session: ' + SESSION_ID);
402
+ console.log(' Recorded: ' + recorder.sessionId);
403
+ if (task) console.log(' Hint: ' + task.substring(0, 100) + (task.length > 100 ? '...' : ''));
404
+ if (scaffoldResult && (scaffoldResult.dirs.length || scaffoldResult.files.length)) {
405
+ console.log(' Scaffolded: ' + (scaffoldResult.dirs.length + scaffoldResult.files.length) + ' new entries (CODING_RULES + docs/state/)');
406
+ }
407
+ console.log('');
408
+
409
+ appendLog('# Knoxis Kit Mode: ' + kitMode);
410
+ appendLog('Session: ' + SESSION_ID);
411
+ if (task) appendLog('Hint: ' + task);
412
+ appendLog('Date: ' + new Date().toISOString());
413
+ appendLog('');
414
+
415
+ const stepKey = 'kit-' + kitMode;
416
+ const stepIdx = recorder.startStep(stepKey, 'Claude Code', tpl.body);
417
+ recorder.setStepPrompt(stepIdx, tpl.body);
418
+ const result = await runClaudeTurn(tpl.body, false);
419
+ appendLog(result.stdout.substring(0, 10000) + '\n');
420
+ recorder.completeStep(stepIdx, result.stdout, result.code !== 0 ? 'exit ' + result.code + (result.stderr ? ': ' + result.stderr.slice(0, 200) : '') : null);
421
+
422
+ console.log('');
423
+ console.log('╔══════════════════════════════════════════════════════════════╗');
424
+ console.log('║ KIT SESSION COMPLETE ║');
425
+ console.log('╚══════════════════════════════════════════════════════════════╝');
426
+ console.log('');
427
+ const logFile = saveSessionLog();
428
+ if (logFile) console.log(' Log: ' + logFile);
429
+ console.log(' Resume: claude --resume ' + SESSION_ID);
430
+ console.log('');
431
+
432
+ await finalizeSession(recorder);
433
+ process.exit(result.code || 0);
434
+ }
435
+
357
436
  // === MAIN ===
358
437
  async function main() {
438
+ const kitMode = process.env.KNOXIS_KIT_MODE || null;
359
439
  const task = loadTask();
360
- if (!task) {
440
+ if (!task && !kitMode) {
361
441
  console.error('No task found. Set KNOXIS_TASK_FILE, pass as CLI argument, or include in CLAUDE.md.');
362
442
  process.exit(1);
363
443
  }
@@ -376,6 +456,17 @@ async function main() {
376
456
  console.warn(' Scaffold failed: ' + e.message);
377
457
  }
378
458
 
459
+ // Kit modes (kickoff/resume) take a different path: run the kit template as
460
+ // a single Claude turn. Skips the 4-phase Groq plan/implement/verify flow —
461
+ // the kit prompt is the framework.
462
+ if (kitMode) {
463
+ if (!kitTemplates.isKitMode(kitMode)) {
464
+ console.error('Unknown KNOXIS_KIT_MODE "' + kitMode + '". Supported: ' + kitTemplates.listModes().join(', '));
465
+ process.exit(1);
466
+ }
467
+ return runKitMode(kitMode, task, identity, scaffoldResult);
468
+ }
469
+
379
470
  const recorder = new SessionRecorder(task, identity.workspace, 'Claude Code + Knoxis (Groq)', {
380
471
  mode: null, // interactive flow isn't a kit mode
381
472
  archetype: null,
@@ -595,6 +595,13 @@ function resolvePairProgramScript() {
595
595
  }
596
596
 
597
597
  const KIT_MODES = new Set(['kickoff', 'resume', 'session-end', 'recovery']);
598
+ // kickoff and resume rely on Claude actually writing files to disk
599
+ // (docs/state/OPEN_QUESTIONS.md and the rest of the context pack). The
600
+ // interactive runner invokes `claude -p --session-id`, which gives Claude
601
+ // proper tool access; pair-program.js pipes to `claude` without -p and
602
+ // silently skips the file writes. Route those two kits through the
603
+ // interactive runner so their kit prompts actually do their work.
604
+ const KIT_MODES_VIA_INTERACTIVE = new Set(['kickoff', 'resume']);
598
605
 
599
606
  function buildPairProgramCommand({ scriptPath, workspace, mode, archetype, pattern, productSlug, projectSlug, taskPrompt, userId, workspaceId, taskIds }) {
600
607
  const q = v => `"${escapeForDoubleQuotedShellArg(v)}"`;
@@ -965,11 +972,28 @@ async function handleRequest(req, res) {
965
972
  // 4-step pipeline. Interactive (Groq) mode and the legacy claude-pipe
966
973
  // fallback remain available when the runner isn't present.
967
974
  const ppScript = resolvePairProgramScript();
968
- if (kitMode && !ppScript) {
975
+ const kitViaInteractive = kitMode && KIT_MODES_VIA_INTERACTIVE.has(kitMode);
976
+ if (kitMode && !kitViaInteractive && !ppScript) {
969
977
  return sendJSON(res, 500, { success: false, error: 'knoxis-pair-program.js not found — reinstall knoxis-helper.' }, requestOrigin);
970
978
  }
971
-
972
- if (kitMode || (ppScript && !interactive && promptText)) {
979
+ if (kitViaInteractive) {
980
+ const scriptPath = resolveInteractiveScript();
981
+ if (!scriptPath) {
982
+ return sendJSON(res, 500, { success: false, error: 'knoxis-interactive-pair.js not found — reinstall knoxis-helper.' }, requestOrigin);
983
+ }
984
+ const kitEnv = { KNOXIS_WORKSPACE_PATH: workspaceDir, KNOXIS_KIT_MODE: kitMode };
985
+ if (promptText) kitEnv.KNOXIS_TASK_FILE = promptFile;
986
+ if (archetype) kitEnv.KNOXIS_KIT_ARCHETYPE = archetype;
987
+ if (pattern) kitEnv.KNOXIS_KIT_PATTERN = pattern;
988
+ if (productSlug) kitEnv.KNOXIS_PRODUCT_SLUG = productSlug;
989
+ if (projectSlug) kitEnv.KNOXIS_PROJECT_SLUG = projectSlug;
990
+ if (portalUserId) kitEnv.KNOXIS_USER_ID = portalUserId;
991
+ if (portalWorkspaceId) kitEnv.KNOXIS_WORKSPACE_ID = portalWorkspaceId;
992
+ if (portalTaskIds && portalTaskIds.length) kitEnv.KNOXIS_TASK_IDS = portalTaskIds.join(',');
993
+ command = buildEnvCommand(kitEnv, `node "${scriptPath}"`);
994
+ mode = `kit:${kitMode}${archetype ? `/${archetype}` : ''}`;
995
+ console.log(`🧰 Kit (interactive runner): ${mode} — ${scriptPath}`);
996
+ } else if (kitMode || (ppScript && !interactive && promptText)) {
973
997
  command = buildPairProgramCommand({
974
998
  scriptPath: ppScript,
975
999
  workspace: workspaceDir,
@@ -1447,9 +1471,35 @@ function connectRelayWebSocket() {
1447
1471
  ? msg.taskIds.filter(t => typeof t === 'string' && t)
1448
1472
  : (typeof msg.taskId === 'string' ? [msg.taskId] : []);
1449
1473
  const ppScript = resolvePairProgramScript();
1450
- const routeViaPairProgram = (kitMode || (ppScript && !interactive && taskPrompt));
1451
-
1452
- if (routeViaPairProgram && ppScript) {
1474
+ const kitViaInteractive = kitMode && KIT_MODES_VIA_INTERACTIVE.has(kitMode);
1475
+ const routeViaPairProgram = !kitViaInteractive && (kitMode || (ppScript && !interactive && taskPrompt));
1476
+
1477
+ if (kitViaInteractive) {
1478
+ const scriptPath = resolveInteractiveScript();
1479
+ if (scriptPath) {
1480
+ // kickoff/resume need Claude's tool path (claude -p --session-id),
1481
+ // which the interactive runner provides. Pass the kit mode + identity
1482
+ // via env so the runner picks the kit branch instead of the 4-phase
1483
+ // Groq flow.
1484
+ if (taskPrompt) {
1485
+ promptFile = path.join(os.tmpdir(), `knoxis-task-${msg.requestId || Date.now()}.txt`);
1486
+ fs.writeFileSync(promptFile, taskPrompt, 'utf8');
1487
+ }
1488
+ const kitEnv = { KNOXIS_WORKSPACE_PATH: wsDir, KNOXIS_KIT_MODE: kitMode };
1489
+ if (promptFile) kitEnv.KNOXIS_TASK_FILE = promptFile;
1490
+ if (archetype) kitEnv.KNOXIS_KIT_ARCHETYPE = archetype;
1491
+ if (pattern) kitEnv.KNOXIS_KIT_PATTERN = pattern;
1492
+ if (productSlug) kitEnv.KNOXIS_PRODUCT_SLUG = productSlug;
1493
+ if (projectSlug) kitEnv.KNOXIS_PROJECT_SLUG = projectSlug;
1494
+ if (portalUserId) kitEnv.KNOXIS_USER_ID = portalUserId;
1495
+ if (portalWorkspaceId) kitEnv.KNOXIS_WORKSPACE_ID = portalWorkspaceId;
1496
+ if (portalTaskIds && portalTaskIds.length) kitEnv.KNOXIS_TASK_IDS = portalTaskIds.join(',');
1497
+ command = buildEnvCommand(kitEnv, `node "${scriptPath}"`);
1498
+ console.log(` 🧰 Kit (interactive runner): ${kitMode}${archetype ? `/${archetype}` : ''} — ${scriptPath}`);
1499
+ } else {
1500
+ console.warn(` ⚠️ knoxis-interactive-pair.js not found — cannot run kit mode ${kitMode}`);
1501
+ }
1502
+ } else if (routeViaPairProgram && ppScript) {
1453
1503
  command = buildPairProgramCommand({
1454
1504
  scriptPath: ppScript,
1455
1505
  workspace: wsDir,
@@ -46,8 +46,29 @@ const STATE_LAYOUT = {
46
46
  overwrite: false
47
47
  },
48
48
  {
49
+ // Three sections: Pending Answers (kickoff/resume interview questions
50
+ // awaiting operator input — the question bus), Active (project-level
51
+ // unresolved questions), Resolved. Kickoff/resume detect "Pending
52
+ // Answers" with `_(fill in)_` placeholders to decide whether to wait
53
+ // or proceed.
49
54
  path: 'docs/state/OPEN_QUESTIONS.md',
50
- content: () => `# Open Questions\n\n## Active\n\n## Resolved\n`,
55
+ content: () => `# Open Questions
56
+
57
+ > **How this file works**
58
+ >
59
+ > - \`## Pending Answers\` — kickoff and resume use this section as a question bus. When the AI needs operator input, it appends numbered entries here with \`**Answer:** _(fill in)_\` placeholders.
60
+ > - To answer: open this file, replace each \`_(fill in)_\` with your answer (plain text, any length), save, then re-run the same kickoff/resume command. The AI consumes the answers, generates the framework files, and moves the entries to \`## Resolved\`.
61
+ > - \`## Active\` — project-level questions you want to track over time but that don't block this session.
62
+ > - \`## Resolved\` — answered questions, kept for history.
63
+
64
+ ## Pending Answers
65
+
66
+ _(Empty — kickoff or resume will populate this when it needs your input.)_
67
+
68
+ ## Active
69
+
70
+ ## Resolved
71
+ `,
51
72
  overwrite: false
52
73
  },
53
74
  {
@@ -2,6 +2,117 @@
2
2
 
3
3
  // Source: docs/Pair Programmer Docs/pair-programmer/framework/01-kickoff.md (v7 — product-centric)
4
4
  // Embedded so the runner is self-contained and versioned with the package.
5
+ //
6
+ // Single-shot adaptation (v8): the helper runs Claude in single-shot mode (no
7
+ // interactive REPL between Claude and the operator). The framework body below
8
+ // is written for an interactive coach. The OPERATING_MODE_PREAMBLE prepended
9
+ // at buildKickoffPrompt() time turns the interview into a file-mediated flow
10
+ // driven by docs/state/OPEN_QUESTIONS.md so single-shot kickoff actually
11
+ // produces the context pack instead of stalling on the first question.
12
+ const OPERATING_MODE_PREAMBLE = `# Operating mode — single-shot, file-mediated (v8)
13
+
14
+ You are running in single-shot mode. There is no interactive REPL with the operator: you produce one response and the process exits. The framework body that follows tells you to ask one question at a time and wait — **ignore that pacing**. Use the file-mediated flow below instead.
15
+
16
+ The interview happens through \`docs/state/OPEN_QUESTIONS.md\`. You write questions there, the operator fills in answers there, the operator re-runs kickoff, you read the answers and produce the context pack. Same questions, same coverage, just batched.
17
+
18
+ ## Step 0 — Read state and pick a branch
19
+
20
+ Before anything else, read \`docs/state/OPEN_QUESTIONS.md\` (if it doesn't exist, scaffold the layout first). It has three sections: \`## Pending Answers\`, \`## Active\`, \`## Resolved\`. Look only at \`## Pending Answers\` to decide which branch you're in.
21
+
22
+ Each pending entry has this shape:
23
+
24
+ \`\`\`markdown
25
+ ### N. <question>
26
+ *Phase: <phase>. Raised: <date>.*
27
+ **Answer:** _(fill in)_
28
+ \`\`\`
29
+
30
+ An entry is **answered** when the \`**Answer:**\` line is anything other than the literal placeholder \`_(fill in)_\` and is non-empty.
31
+
32
+ Branch:
33
+ - **A. No \`## Pending Answers\` section, or section is empty** → State A (first run, generate questions).
34
+ - **B. \`## Pending Answers\` exists with at least one entry whose answer is still \`_(fill in)_\`** → State B (waiting on operator).
35
+ - **C. \`## Pending Answers\` exists and every entry is answered** → State C (consume answers, write context pack).
36
+
37
+ ## State A — Generate questions
38
+
39
+ Scaffold the layout if missing (\`docs/state/\`, \`docs/product/\`, \`docs/architecture/adr/\`, \`docs/planning/\`, plus \`OPEN_QUESTIONS.md\` with \`## Pending Answers\`, \`## Active\`, \`## Resolved\` sections).
40
+
41
+ Walk through Phases -1 through 6 of the framework body below and **batch every question you would ask** into \`docs/state/OPEN_QUESTIONS.md\` under \`## Pending Answers\`. Number them sequentially. Tag each with the phase it belongs to. Group conceptually but keep numbering global. For each question:
42
+
43
+ - Be specific — the operator answers in plain text, no follow-up.
44
+ - Where the framework offers a default or 3–4 options, list them inline so the operator can just pick.
45
+ - Where Phase -1 already has portal-provided product context (a \`product.local.json\` exists in the parent dir or was pasted), pre-fill those answers instead of re-asking.
46
+
47
+ Format each entry exactly:
48
+
49
+ \`\`\`markdown
50
+ ### N. <question, including any options>
51
+ *Phase: <-1|0|1|2|3|4|5|6>. Raised: <today's ISO date>.*
52
+ **Answer:** _(fill in)_
53
+ \`\`\`
54
+
55
+ Do **not** write README.md, CLAUDE.md, PRD.md, ARCHITECTURE.md, BACKLOG.md, or any other context-pack file in this state. Only OPEN_QUESTIONS.md and the directory scaffold.
56
+
57
+ End your response with a short block telling the operator exactly what to do next:
58
+
59
+ \`\`\`
60
+ Kickoff interview written to docs/state/OPEN_QUESTIONS.md (<count> questions).
61
+
62
+ Next:
63
+ 1. Open docs/state/OPEN_QUESTIONS.md
64
+ 2. Fill in **Answer:** lines (replace \`_(fill in)_\`)
65
+ 3. Re-run: knoxis-pair-program --mode kickoff --workspace <name>
66
+
67
+ The framework files (README, PRD, ARCHITECTURE, etc.) will be generated on the next run once answers are present.
68
+ \`\`\`
69
+
70
+ Then stop.
71
+
72
+ ## State B — Waiting on operator
73
+
74
+ Do not re-ask. Do not re-write OPEN_QUESTIONS.md. Do not write the context pack.
75
+
76
+ Read \`## Pending Answers\` and list the question numbers that still have \`_(fill in)_\`. Print:
77
+
78
+ \`\`\`
79
+ Kickoff is waiting on answers in docs/state/OPEN_QUESTIONS.md.
80
+
81
+ Unanswered: #<n>, #<n>, #<n>
82
+ Answered: #<n>, #<n>
83
+
84
+ Fill in the remaining **Answer:** lines and re-run kickoff to generate the context pack.
85
+ \`\`\`
86
+
87
+ Then stop.
88
+
89
+ ## State C — Consume answers and write the context pack
90
+
91
+ Now execute Phases 1 through 8 of the framework body below in one pass, using the answers as your source of truth. Specifically:
92
+
93
+ 1. Read every \`### N. <question>\` / \`**Answer:** ...\` pair in \`## Pending Answers\`.
94
+ 2. Resolve each phase's questions against those answers. If an answer is ambiguous, pick the most pragmatic interpretation and note the assumption in DECISIONS.md rather than asking again.
95
+ 3. Write the full context pack — README.md, CLAUDE.md, docs/product/PRD.md, docs/architecture/ARCHITECTURE.md (and any ADRs), docs/product/GLOSSARY.md, docs/planning/EXECUTION_PLAN.md, docs/planning/BACKLOG.md (with acceptance criteria), and the state files (STATUS.md, HANDOFF.md, DECISIONS.md, CHANGELOG.md, RISKS.md, KICKOFF.md). Apply the YAML frontmatter convention from the framework body.
96
+ 4. Move every \`### N.\` from \`## Pending Answers\` to \`## Resolved\` in OPEN_QUESTIONS.md, preserving the question text and adding a one-line resolution note (e.g. *"Resolved <date>: captured in PRD / CLAUDE.md / D-1 / etc."*). Leave the \`## Pending Answers\` section header in place but empty.
97
+ 5. Any genuinely unresolved item that emerged during the write-up (TBDs the answers didn't cover) goes under \`## Active\` — not \`## Pending Answers\`. Pending Answers is reserved for kickoff/resume interview questions awaiting operator input; Active is for project-level open questions.
98
+ 6. Append a one-line entry to docs/state/CHANGELOG.md under \`## [Unreleased]\` → \`### Added\`: \`Project initialized via kickoff (session <id>).\`
99
+
100
+ End your response with a tour of what was written (file paths only, not full contents) and the Session 1 kickoff prompt in a copyable code block, per Phase 9.
101
+
102
+ ## Operator identity / session id
103
+
104
+ Use \`KNOXIS_USER_ID\` from the environment (or whatever the framework body's "operator" question collected). Generate a session ID locally if no portal MCP is connected: \`s-<YYYY-MM-DD>-<HHmm>\`. Tag every artifact's frontmatter with this.
105
+
106
+ ## Non-negotiables
107
+
108
+ - Always write to disk in States A and C. Do not describe what you would write.
109
+ - Never wait for operator input mid-response.
110
+ - Never overwrite an existing file unless the framework body explicitly says so for that file. STATUS.md, HANDOFF.md, CHANGELOG.md, OPEN_QUESTIONS.md may be appended/updated; README.md, CLAUDE.md, PRD.md, ARCHITECTURE.md must not exist already in State C (if they do, halt and report — that means kickoff already ran).
111
+
112
+ ---
113
+
114
+ `;
115
+
5
116
  const KICKOFF_BODY = `# 01 — Project Kickoff (Coached, v7)
6
117
 
7
118
  > Use at the start of a new project. Generates the full context pack: README, PRD, ARCHITECTURE, CLAUDE.md, GLOSSARY, EXECUTION_PLAN, BACKLOG (with acceptance criteria), state scaffolding, and the Session 1 kickoff prompt.
@@ -534,11 +645,11 @@ function buildKickoffPrompt({ taskDescription, productSlug, projectSlug, workspa
534
645
  if (workspace) header.push(`Workspace: ${workspace}`);
535
646
  if (taskDescription) header.push(`Initial task hint: ${taskDescription}`);
536
647
  const ctx = header.length ? `Session context:\n${header.join('\n')}\n\n` : '';
537
- return ctx + KICKOFF_BODY;
648
+ return ctx + OPERATING_MODE_PREAMBLE + KICKOFF_BODY;
538
649
  }
539
650
 
540
651
  module.exports = {
541
652
  title: 'Project Kickoff',
542
- body: KICKOFF_BODY,
653
+ body: OPERATING_MODE_PREAMBLE + KICKOFF_BODY,
543
654
  buildPrompt: buildKickoffPrompt
544
655
  };
@@ -1,6 +1,107 @@
1
1
  'use strict';
2
2
 
3
3
  // Source: docs/Pair Programmer Docs/pair-programmer/framework/02-resume.md (v3 — product-centric)
4
+ //
5
+ // Single-shot adaptation (v4): same as kickoff — the framework body is written
6
+ // for an interactive coach but the helper runs Claude in single-shot mode. The
7
+ // OPERATING_MODE_PREAMBLE prepended at buildResumePrompt() time turns resume
8
+ // into a self-driving session that updates state files at the end and uses
9
+ // docs/state/OPEN_QUESTIONS.md as the question bus when something genuinely
10
+ // can't be decided autonomously.
11
+ const OPERATING_MODE_PREAMBLE = `# Operating mode — single-shot, file-mediated (v4)
12
+
13
+ You are running in single-shot mode. There is no interactive REPL with the operator: you produce one response and the process exits. The framework body that follows tells you to confirm direction, ask about archetype, and wait between steps — **ignore that pacing**. Use the file-mediated flow below.
14
+
15
+ The handshake with the operator happens through \`docs/state/OPEN_QUESTIONS.md\`. If you genuinely can't proceed without operator input, write the question there under \`## Pending Answers\` and stop. Otherwise, do the work and update state files at the end.
16
+
17
+ ## Step 0 — Read state and pick a branch
18
+
19
+ Read \`docs/state/OPEN_QUESTIONS.md\` first. It has three sections: \`## Pending Answers\` (kickoff/resume interview questions waiting on the operator), \`## Active\` (project-level unresolved questions), \`## Resolved\`. An entry under \`## Pending Answers\` is **answered** when the \`**Answer:**\` line is anything other than the literal placeholder \`_(fill in)_\` and is non-empty.
20
+
21
+ Branch:
22
+ - **A. \`## Pending Answers\` exists with at least one entry whose answer is still \`_(fill in)_\`** → State A (waiting on operator).
23
+ - **B. \`## Pending Answers\` is empty or absent, or every entry there is answered** → State B (do the work).
24
+
25
+ ## State A — Waiting on operator
26
+
27
+ Do not start work. Do not edit code. Do not write any other state files.
28
+
29
+ Print:
30
+
31
+ \`\`\`
32
+ Resume blocked — waiting on answers in docs/state/OPEN_QUESTIONS.md.
33
+
34
+ Unanswered: #<n>, #<n>
35
+ Answered: #<n>, #<n>
36
+
37
+ Fill in the remaining **Answer:** lines and re-run resume.
38
+ \`\`\`
39
+
40
+ If any answered entries from a previous run are now ready to consume, briefly note what you'd do with them — but still don't act until **all** Pending Answers are resolved (the operator may be answering in batches).
41
+
42
+ Then stop.
43
+
44
+ ## State B — Do the work
45
+
46
+ Read CLAUDE.md, docs/state/STATUS.md, docs/state/HANDOFF.md, docs/state/DECISIONS.md, and the \`## Active\` section of OPEN_QUESTIONS.md to orient. The framework body below describes the archetype rituals (feature / bug / refactor / spike / review). Follow the matching ritual but **do not pause to ask the operator** which archetype this is — the runner passed an \`Archetype hint\` in the session context header above; use it. If absent, infer from the operator note / HANDOFF / pre-flight (default \`feature\`).
47
+
48
+ Work autonomously. Make decisions. Where the framework says "wait for confirmation," substitute "make the most pragmatic call and capture the assumption in DECISIONS.md."
49
+
50
+ If you hit something you genuinely can't decide alone — a security/compliance question, a missing credential, a contradiction between PRD and STATUS, a waiver request, an architectural fork that warrants the operator's call — append it to \`## Pending Answers\` in OPEN_QUESTIONS.md using the same shape kickoff uses:
51
+
52
+ \`\`\`markdown
53
+ ### N. <question>
54
+ *Phase: resume. Raised: <today's ISO date>.*
55
+ **Answer:** _(fill in)_
56
+ \`\`\`
57
+
58
+ Then stop with a short note pointing at the new question(s). Do not partially-finish code.
59
+
60
+ ## Step N — Non-negotiable state update at end of work
61
+
62
+ Before your response ends, **write the following files**. Do not describe them — actually write them. The QIG dashboard reads these; skipping any of them means the team loses visibility on what happened.
63
+
64
+ 1. **\`docs/state/STATUS.md\`** — overwrite. Update \`_Last updated:\` line. Sections:
65
+ - \`## Done\`: one bullet per concrete thing completed in this session.
66
+ - \`## In flight\`: anything started but not finished. Empty if none.
67
+ - \`## Next\`: ONE concrete next step.
68
+ - \`## Notes\`: caveats. Empty if none.
69
+
70
+ 2. **\`docs/state/HANDOFF.md\`** — overwrite the whole file:
71
+ - \`## Where I stopped\`: 2–4 sentences. Concrete: file, function, feature, state.
72
+ - \`## Why I stopped here\`: brief.
73
+ - \`## First thing to do next session\`: ONE concrete action with file:line if relevant.
74
+ - \`## Landmines / gotchas\`: anything surprising. \`None.\` if none.
75
+ - \`## Environment state\`: running services, env vars, branches. \`None.\` if none.
76
+
77
+ 3. **\`docs/state/CHANGELOG.md\`** — if code shipped or behavior changed, append a one-line bullet under \`## [Unreleased]\` in the right section (Added/Changed/Fixed/Removed). Skip if nothing shipped.
78
+
79
+ 4. **\`docs/state/DECISIONS.md\`** — if a non-trivial choice was made (including assumptions you locked in to avoid stalling), append:
80
+ \`\`\`
81
+ ## D-<n>: <one-line title>
82
+ **Context:** <2 sentences>
83
+ **Decision:** <what you chose>
84
+ **Alternatives considered:** <what else and why not>
85
+ **Consequences:** <implications>
86
+ \`\`\`
87
+ Skip only if no real decisions were made.
88
+
89
+ 5. **\`docs/state/OPEN_QUESTIONS.md\`** — move any newly-resolved Pending Answers to \`## Resolved\` with a one-line resolution note. Append any new project-level unknowns to \`## Active\`. Append any new operator-blocking questions to \`## Pending Answers\` per State B above.
90
+
91
+ 6. **\`docs/state/RISKS.md\`** — append any newly identified risks; update status of existing watched risks if they materialized / dissolved this session.
92
+
93
+ End your response with a one-paragraph summary plus a list of files written and skipped (with reason for skips).
94
+
95
+ ## Non-negotiables
96
+
97
+ - Always update STATUS.md and HANDOFF.md, even if the session was tiny.
98
+ - Never wait for operator input mid-response. Use OPEN_QUESTIONS.md \`## Pending Answers\` as the question bus.
99
+ - Never partially apply changes and stop. Either finish a logical unit and update state, or revert to a clean tree and surface the blocker via OPEN_QUESTIONS.md.
100
+
101
+ ---
102
+
103
+ `;
104
+
4
105
  const RESUME_HEAD = `# 02 — Session Resume (Coached, v3)
5
106
 
6
107
  > Paste at the start of every working session after Session 1. Claude reads project state, refreshes product context from the portal, runs pre-flight checks, confirms direction with you, identifies the session archetype, and adapts. Apply the standard coding ruleset (\`05-coding-ruleset.md\` v3; rules referenced by ID).
@@ -288,12 +389,12 @@ function buildResumePrompt({ archetype, taskDescription, productSlug, projectSlu
288
389
  archetypeBlock = ALL_ARCHETYPES;
289
390
  }
290
391
 
291
- return ctx + RESUME_HEAD + archetypeBlock + RESUME_TAIL;
392
+ return ctx + OPERATING_MODE_PREAMBLE + RESUME_HEAD + archetypeBlock + RESUME_TAIL;
292
393
  }
293
394
 
294
395
  module.exports = {
295
396
  title: 'Session Resume',
296
- body: RESUME_HEAD + ALL_ARCHETYPES + RESUME_TAIL,
397
+ body: OPERATING_MODE_PREAMBLE + RESUME_HEAD + ALL_ARCHETYPES + RESUME_TAIL,
297
398
  buildPrompt: buildResumePrompt,
298
399
  archetypes: Object.keys(ARCHETYPE_MAP)
299
400
  };
@@ -242,13 +242,19 @@ Show me the changes. Confirm path.
242
242
 
243
243
  ## Step 7 — Update RISKS.md
244
244
 
245
- **Orientation:** "RISKS is what could go wrong, watched over time."
245
+ **Orientation:** "RISKS is what could go wrong, watched over time. Two updates each session: add anything new that surfaced, update the status of any existing risk that materialized, dissolved, or shifted."
246
246
 
247
- Most sessions: nothing changes here. That's fine. Just ask:
247
+ Walk through:
248
+
249
+ - **New risks to add.** Scan the session for risk candidates: technical debt introduced, brittle integrations, unhandled edge cases, security or compliance concerns, deferred work, dependencies on unstable upstream systems, scope creep, fragile tests. Propose what you noticed:
250
+
251
+ > "Here are risks I noticed this session: <list 0–3 candidates with one-line framing>. Anything else?"
252
+
253
+ - **Existing risks to update.** Read the current RISKS.md. For each watched risk, ask whether anything changed this session — did it become real, get mitigated, or dissolve?
248
254
 
249
- > "Anything I should add to RISKS? New risks I noticed, old risks that became real, old risks that went away?"
255
+ > "Looking at the watched risks: <list current R-<n> ids and one-line summaries>. Any status changes?"
250
256
 
251
- If yes, format:
257
+ For each new risk, append:
252
258
 
253
259
  \`\`\`markdown
254
260
  ## R-<number>: <one-line risk>
@@ -259,7 +265,11 @@ If yes, format:
259
265
  - *Status:* watching / mitigated / materialized / dissolved
260
266
  \`\`\`
261
267
 
262
- If nothing, skip and note "no risk updates this session."
268
+ For status changes on existing risks, update the *Status:* line in place and note the change in the Step 10 summary.
269
+
270
+ If nothing genuinely surfaced and no existing risk shifted, say so explicitly. Don't fabricate to fill the file.
271
+
272
+ Show me the changes. Confirm path.
263
273
 
264
274
  ## Step 8 — Rule events, waivers, incidents
265
275
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knoxis-helper",
3
- "version": "1.6.4",
3
+ "version": "1.7.2",
4
4
  "description": "Local helper for Knoxis pair programming - connects your machine to Knoxis on qig.ai",
5
5
  "bin": {
6
6
  "knoxis-helper": "./bin/knoxis-helper.js"