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.
- package/lib/framework-version.js +4 -4
- package/lib/knoxis-interactive-pair.js +92 -1
- package/lib/knoxis-local-agent.js +56 -6
- package/lib/state-scaffold.js +22 -1
- package/lib/templates/kickoff.js +113 -2
- package/lib/templates/resume.js +103 -2
- package/lib/templates/session-end.js +15 -5
- package/package.json +1 -1
package/lib/framework-version.js
CHANGED
|
@@ -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.
|
|
15
|
+
bundle: '1.1.0',
|
|
16
16
|
documents: {
|
|
17
17
|
overview: 1,
|
|
18
|
-
kickoff:
|
|
19
|
-
resume:
|
|
20
|
-
sessionEnd:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1451
|
-
|
|
1452
|
-
|
|
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,
|
package/lib/state-scaffold.js
CHANGED
|
@@ -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
|
|
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
|
{
|
package/lib/templates/kickoff.js
CHANGED
|
@@ -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
|
};
|
package/lib/templates/resume.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
> "
|
|
255
|
+
> "Looking at the watched risks: <list current R-<n> ids and one-line summaries>. Any status changes?"
|
|
250
256
|
|
|
251
|
-
|
|
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
|
-
|
|
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
|
|