knoxis-helper 1.7.1 → 1.8.1
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/bin/knoxis-helper.js
CHANGED
|
@@ -397,7 +397,7 @@ async function main() {
|
|
|
397
397
|
console.log(`knoxis-helper@${pkgVersion}`);
|
|
398
398
|
if (fw) {
|
|
399
399
|
console.log(`framework bundle: v${fw.bundle}`);
|
|
400
|
-
console.log(` kickoff v${fw.documents.kickoff}, resume v${fw.documents.resume}, session-end v${fw.documents.sessionEnd}, recovery v${fw.documents.recovery}, ruleset v${fw.documents.codingRuleset}, portal-contract v${fw.documents.portalContract}`);
|
|
400
|
+
console.log(` kickoff v${fw.documents.kickoff}, feature-kickoff v${fw.documents.featureKickoff}, resume v${fw.documents.resume}, session-end v${fw.documents.sessionEnd}, recovery v${fw.documents.recovery}, ruleset v${fw.documents.codingRuleset}, portal-contract v${fw.documents.portalContract}`);
|
|
401
401
|
}
|
|
402
402
|
process.exit(0);
|
|
403
403
|
}
|
package/lib/framework-version.js
CHANGED
|
@@ -12,10 +12,11 @@
|
|
|
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.2.0',
|
|
16
16
|
documents: {
|
|
17
17
|
overview: 1,
|
|
18
18
|
kickoff: 8,
|
|
19
|
+
featureKickoff: 1,
|
|
19
20
|
resume: 4,
|
|
20
21
|
sessionEnd: 4,
|
|
21
22
|
recovery: 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,95 @@ 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
|
+
// feature-kickoff additionally consumes identity (userId, workspaceId,
|
|
371
|
+
// parent task) so the manifest can carry portal linkage. The other kit
|
|
372
|
+
// templates ignore unknown fields, so passing them through is safe.
|
|
373
|
+
tpl = kitTemplates.getTemplate(kitMode, {
|
|
374
|
+
taskDescription: task || null,
|
|
375
|
+
archetype,
|
|
376
|
+
pattern,
|
|
377
|
+
productSlug: identity.productSlug,
|
|
378
|
+
projectSlug: identity.projectSlug,
|
|
379
|
+
workspace: identity.workspace,
|
|
380
|
+
userId: identity.userId,
|
|
381
|
+
workspaceId: identity.workspaceId,
|
|
382
|
+
parentTaskId: (identity.taskIds && identity.taskIds.length) ? identity.taskIds[0] : null
|
|
383
|
+
});
|
|
384
|
+
} catch (e) {
|
|
385
|
+
console.error('Kit template error: ' + e.message);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const recorder = new SessionRecorder(task || `[${kitMode} session]`, identity.workspace, 'Claude Code (kit)', {
|
|
390
|
+
mode: kitMode,
|
|
391
|
+
archetype,
|
|
392
|
+
productSlug: identity.productSlug,
|
|
393
|
+
projectSlug: identity.projectSlug,
|
|
394
|
+
engineerId: identity.engineerId,
|
|
395
|
+
userId: identity.userId,
|
|
396
|
+
workspaceId: identity.workspaceId,
|
|
397
|
+
taskIds: identity.taskIds
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
console.log('');
|
|
401
|
+
console.log('╔══════════════════════════════════════════════════════════════╗');
|
|
402
|
+
console.log('║ KNOXIS KIT MODE: ' + kitMode.toUpperCase().padEnd(36) + '║');
|
|
403
|
+
console.log('╚══════════════════════════════════════════════════════════════╝');
|
|
404
|
+
console.log('');
|
|
405
|
+
console.log(' Mode: ' + kitMode + (archetype ? ' (archetype: ' + archetype + ')' : ''));
|
|
406
|
+
console.log(' Workspace: ' + identity.workspace);
|
|
407
|
+
console.log(' Session: ' + SESSION_ID);
|
|
408
|
+
console.log(' Recorded: ' + recorder.sessionId);
|
|
409
|
+
if (task) console.log(' Hint: ' + task.substring(0, 100) + (task.length > 100 ? '...' : ''));
|
|
410
|
+
if (scaffoldResult && (scaffoldResult.dirs.length || scaffoldResult.files.length)) {
|
|
411
|
+
console.log(' Scaffolded: ' + (scaffoldResult.dirs.length + scaffoldResult.files.length) + ' new entries (CODING_RULES + docs/state/)');
|
|
412
|
+
}
|
|
413
|
+
console.log('');
|
|
414
|
+
|
|
415
|
+
appendLog('# Knoxis Kit Mode: ' + kitMode);
|
|
416
|
+
appendLog('Session: ' + SESSION_ID);
|
|
417
|
+
if (task) appendLog('Hint: ' + task);
|
|
418
|
+
appendLog('Date: ' + new Date().toISOString());
|
|
419
|
+
appendLog('');
|
|
420
|
+
|
|
421
|
+
const stepKey = 'kit-' + kitMode;
|
|
422
|
+
const stepIdx = recorder.startStep(stepKey, 'Claude Code', tpl.body);
|
|
423
|
+
recorder.setStepPrompt(stepIdx, tpl.body);
|
|
424
|
+
const result = await runClaudeTurn(tpl.body, false);
|
|
425
|
+
appendLog(result.stdout.substring(0, 10000) + '\n');
|
|
426
|
+
recorder.completeStep(stepIdx, result.stdout, result.code !== 0 ? 'exit ' + result.code + (result.stderr ? ': ' + result.stderr.slice(0, 200) : '') : null);
|
|
427
|
+
|
|
428
|
+
console.log('');
|
|
429
|
+
console.log('╔══════════════════════════════════════════════════════════════╗');
|
|
430
|
+
console.log('║ KIT SESSION COMPLETE ║');
|
|
431
|
+
console.log('╚══════════════════════════════════════════════════════════════╝');
|
|
432
|
+
console.log('');
|
|
433
|
+
const logFile = saveSessionLog();
|
|
434
|
+
if (logFile) console.log(' Log: ' + logFile);
|
|
435
|
+
console.log(' Resume: claude --resume ' + SESSION_ID);
|
|
436
|
+
console.log('');
|
|
437
|
+
|
|
438
|
+
await finalizeSession(recorder);
|
|
439
|
+
process.exit(result.code || 0);
|
|
440
|
+
}
|
|
441
|
+
|
|
357
442
|
// === MAIN ===
|
|
358
443
|
async function main() {
|
|
444
|
+
const kitMode = process.env.KNOXIS_KIT_MODE || null;
|
|
359
445
|
const task = loadTask();
|
|
360
|
-
if (!task) {
|
|
446
|
+
if (!task && !kitMode) {
|
|
361
447
|
console.error('No task found. Set KNOXIS_TASK_FILE, pass as CLI argument, or include in CLAUDE.md.');
|
|
362
448
|
process.exit(1);
|
|
363
449
|
}
|
|
@@ -376,6 +462,17 @@ async function main() {
|
|
|
376
462
|
console.warn(' Scaffold failed: ' + e.message);
|
|
377
463
|
}
|
|
378
464
|
|
|
465
|
+
// Kit modes (kickoff/resume) take a different path: run the kit template as
|
|
466
|
+
// a single Claude turn. Skips the 4-phase Groq plan/implement/verify flow —
|
|
467
|
+
// the kit prompt is the framework.
|
|
468
|
+
if (kitMode) {
|
|
469
|
+
if (!kitTemplates.isKitMode(kitMode)) {
|
|
470
|
+
console.error('Unknown KNOXIS_KIT_MODE "' + kitMode + '". Supported: ' + kitTemplates.listModes().join(', '));
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
return runKitMode(kitMode, task, identity, scaffoldResult);
|
|
474
|
+
}
|
|
475
|
+
|
|
379
476
|
const recorder = new SessionRecorder(task, identity.workspace, 'Claude Code + Knoxis (Groq)', {
|
|
380
477
|
mode: null, // interactive flow isn't a kit mode
|
|
381
478
|
archetype: null,
|
|
@@ -594,7 +594,15 @@ function resolvePairProgramScript() {
|
|
|
594
594
|
return null;
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
const KIT_MODES = new Set(['kickoff', 'resume', 'session-end', 'recovery']);
|
|
597
|
+
const KIT_MODES = new Set(['kickoff', 'feature-kickoff', 'resume', 'session-end', 'recovery']);
|
|
598
|
+
// kickoff, feature-kickoff, and resume rely on Claude actually writing files
|
|
599
|
+
// to disk (docs/state/OPEN_QUESTIONS.md, docs/features/<slug>/*, and the rest
|
|
600
|
+
// of the context pack). The interactive runner invokes `claude -p
|
|
601
|
+
// --session-id`, which gives Claude proper tool access; pair-program.js pipes
|
|
602
|
+
// to `claude` without -p and silently skips the file writes. Route those
|
|
603
|
+
// kits through the interactive runner so their kit prompts actually do
|
|
604
|
+
// their work.
|
|
605
|
+
const KIT_MODES_VIA_INTERACTIVE = new Set(['kickoff', 'feature-kickoff', 'resume']);
|
|
598
606
|
|
|
599
607
|
function buildPairProgramCommand({ scriptPath, workspace, mode, archetype, pattern, productSlug, projectSlug, taskPrompt, userId, workspaceId, taskIds }) {
|
|
600
608
|
const q = v => `"${escapeForDoubleQuotedShellArg(v)}"`;
|
|
@@ -965,11 +973,28 @@ async function handleRequest(req, res) {
|
|
|
965
973
|
// 4-step pipeline. Interactive (Groq) mode and the legacy claude-pipe
|
|
966
974
|
// fallback remain available when the runner isn't present.
|
|
967
975
|
const ppScript = resolvePairProgramScript();
|
|
968
|
-
|
|
976
|
+
const kitViaInteractive = kitMode && KIT_MODES_VIA_INTERACTIVE.has(kitMode);
|
|
977
|
+
if (kitMode && !kitViaInteractive && !ppScript) {
|
|
969
978
|
return sendJSON(res, 500, { success: false, error: 'knoxis-pair-program.js not found — reinstall knoxis-helper.' }, requestOrigin);
|
|
970
979
|
}
|
|
971
|
-
|
|
972
|
-
|
|
980
|
+
if (kitViaInteractive) {
|
|
981
|
+
const scriptPath = resolveInteractiveScript();
|
|
982
|
+
if (!scriptPath) {
|
|
983
|
+
return sendJSON(res, 500, { success: false, error: 'knoxis-interactive-pair.js not found — reinstall knoxis-helper.' }, requestOrigin);
|
|
984
|
+
}
|
|
985
|
+
const kitEnv = { KNOXIS_WORKSPACE_PATH: workspaceDir, KNOXIS_KIT_MODE: kitMode };
|
|
986
|
+
if (promptText) kitEnv.KNOXIS_TASK_FILE = promptFile;
|
|
987
|
+
if (archetype) kitEnv.KNOXIS_KIT_ARCHETYPE = archetype;
|
|
988
|
+
if (pattern) kitEnv.KNOXIS_KIT_PATTERN = pattern;
|
|
989
|
+
if (productSlug) kitEnv.KNOXIS_PRODUCT_SLUG = productSlug;
|
|
990
|
+
if (projectSlug) kitEnv.KNOXIS_PROJECT_SLUG = projectSlug;
|
|
991
|
+
if (portalUserId) kitEnv.KNOXIS_USER_ID = portalUserId;
|
|
992
|
+
if (portalWorkspaceId) kitEnv.KNOXIS_WORKSPACE_ID = portalWorkspaceId;
|
|
993
|
+
if (portalTaskIds && portalTaskIds.length) kitEnv.KNOXIS_TASK_IDS = portalTaskIds.join(',');
|
|
994
|
+
command = buildEnvCommand(kitEnv, `node "${scriptPath}"`);
|
|
995
|
+
mode = `kit:${kitMode}${archetype ? `/${archetype}` : ''}`;
|
|
996
|
+
console.log(`🧰 Kit (interactive runner): ${mode} — ${scriptPath}`);
|
|
997
|
+
} else if (kitMode || (ppScript && !interactive && promptText)) {
|
|
973
998
|
command = buildPairProgramCommand({
|
|
974
999
|
scriptPath: ppScript,
|
|
975
1000
|
workspace: workspaceDir,
|
|
@@ -1447,9 +1472,35 @@ function connectRelayWebSocket() {
|
|
|
1447
1472
|
? msg.taskIds.filter(t => typeof t === 'string' && t)
|
|
1448
1473
|
: (typeof msg.taskId === 'string' ? [msg.taskId] : []);
|
|
1449
1474
|
const ppScript = resolvePairProgramScript();
|
|
1450
|
-
const
|
|
1451
|
-
|
|
1452
|
-
|
|
1475
|
+
const kitViaInteractive = kitMode && KIT_MODES_VIA_INTERACTIVE.has(kitMode);
|
|
1476
|
+
const routeViaPairProgram = !kitViaInteractive && (kitMode || (ppScript && !interactive && taskPrompt));
|
|
1477
|
+
|
|
1478
|
+
if (kitViaInteractive) {
|
|
1479
|
+
const scriptPath = resolveInteractiveScript();
|
|
1480
|
+
if (scriptPath) {
|
|
1481
|
+
// kickoff/resume need Claude's tool path (claude -p --session-id),
|
|
1482
|
+
// which the interactive runner provides. Pass the kit mode + identity
|
|
1483
|
+
// via env so the runner picks the kit branch instead of the 4-phase
|
|
1484
|
+
// Groq flow.
|
|
1485
|
+
if (taskPrompt) {
|
|
1486
|
+
promptFile = path.join(os.tmpdir(), `knoxis-task-${msg.requestId || Date.now()}.txt`);
|
|
1487
|
+
fs.writeFileSync(promptFile, taskPrompt, 'utf8');
|
|
1488
|
+
}
|
|
1489
|
+
const kitEnv = { KNOXIS_WORKSPACE_PATH: wsDir, KNOXIS_KIT_MODE: kitMode };
|
|
1490
|
+
if (promptFile) kitEnv.KNOXIS_TASK_FILE = promptFile;
|
|
1491
|
+
if (archetype) kitEnv.KNOXIS_KIT_ARCHETYPE = archetype;
|
|
1492
|
+
if (pattern) kitEnv.KNOXIS_KIT_PATTERN = pattern;
|
|
1493
|
+
if (productSlug) kitEnv.KNOXIS_PRODUCT_SLUG = productSlug;
|
|
1494
|
+
if (projectSlug) kitEnv.KNOXIS_PROJECT_SLUG = projectSlug;
|
|
1495
|
+
if (portalUserId) kitEnv.KNOXIS_USER_ID = portalUserId;
|
|
1496
|
+
if (portalWorkspaceId) kitEnv.KNOXIS_WORKSPACE_ID = portalWorkspaceId;
|
|
1497
|
+
if (portalTaskIds && portalTaskIds.length) kitEnv.KNOXIS_TASK_IDS = portalTaskIds.join(',');
|
|
1498
|
+
command = buildEnvCommand(kitEnv, `node "${scriptPath}"`);
|
|
1499
|
+
console.log(` 🧰 Kit (interactive runner): ${kitMode}${archetype ? `/${archetype}` : ''} — ${scriptPath}`);
|
|
1500
|
+
} else {
|
|
1501
|
+
console.warn(` ⚠️ knoxis-interactive-pair.js not found — cannot run kit mode ${kitMode}`);
|
|
1502
|
+
}
|
|
1503
|
+
} else if (routeViaPairProgram && ppScript) {
|
|
1453
1504
|
command = buildPairProgramCommand({
|
|
1454
1505
|
scriptPath: ppScript,
|
|
1455
1506
|
workspace: wsDir,
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Feature Kickoff — single-shot, autonomous (v1)
|
|
4
|
+
//
|
|
5
|
+
// Where project-kickoff sets up a brand-new project context pack, feature-kickoff
|
|
6
|
+
// takes a single feature task from the operator, decomposes it into a phased
|
|
7
|
+
// roadmap, and writes the roadmap + phase plan + machine-readable manifest into
|
|
8
|
+
// docs/features/<slug>/. Multiple features can coexist; each is independently
|
|
9
|
+
// trackable from the QIG dashboard via its manifest.json.
|
|
10
|
+
//
|
|
11
|
+
// State machine (similar shape to resume.js, autonomous-first):
|
|
12
|
+
// A: docs/features/<slug>/ missing → write the roadmap
|
|
13
|
+
// B: directory exists with unanswered _(fill in)_ entries → wait
|
|
14
|
+
// C: directory exists, no pending answers → refresh in place
|
|
15
|
+
|
|
16
|
+
const FEATURE_KICKOFF_BODY = `# Feature Kickoff (single-shot, autonomous)
|
|
17
|
+
|
|
18
|
+
You are running in single-shot mode. You produce one response and the process
|
|
19
|
+
exits. Your job is to take the operator's feature description (the task in the
|
|
20
|
+
session header above), decompose it into a concrete phased roadmap, and write
|
|
21
|
+
the roadmap + phase plan + manifest to disk so the QIG dashboard can track it.
|
|
22
|
+
|
|
23
|
+
This is **planning only** — you do not change application code in this run.
|
|
24
|
+
|
|
25
|
+
## Step 0 — Orient on the project
|
|
26
|
+
|
|
27
|
+
Read these files first (whichever exist) to ground your plan in the codebase:
|
|
28
|
+
|
|
29
|
+
- \`CLAUDE.md\`
|
|
30
|
+
- \`README.md\`
|
|
31
|
+
- \`docs/state/STATUS.md\`
|
|
32
|
+
- \`docs/state/HANDOFF.md\`
|
|
33
|
+
- \`docs/state/DECISIONS.md\`
|
|
34
|
+
- \`docs/architecture/ARCHITECTURE.md\` (if present)
|
|
35
|
+
- Any entry-point files mentioned in CLAUDE.md
|
|
36
|
+
|
|
37
|
+
If the project has none of this, still proceed — feature-kickoff does not
|
|
38
|
+
require project-kickoff to have run first.
|
|
39
|
+
|
|
40
|
+
## Step 1 — Pick a feature slug
|
|
41
|
+
|
|
42
|
+
Derive a slug from the feature description in the session header. Lowercase,
|
|
43
|
+
dashes, ≤40 chars, semantically descriptive. Examples:
|
|
44
|
+
|
|
45
|
+
- "Add real-time notification toasts" → \`realtime-notification-toasts\`
|
|
46
|
+
- "Replace polling with SSE on the dashboard" → \`dashboard-sse-replacement\`
|
|
47
|
+
|
|
48
|
+
Do not include the words "feature" or "task" in the slug.
|
|
49
|
+
|
|
50
|
+
## Step 2 — State-branch detection
|
|
51
|
+
|
|
52
|
+
Check \`docs/features/<slug>/\`:
|
|
53
|
+
|
|
54
|
+
- **State A — directory missing.** First run for this feature. Continue to Step 3.
|
|
55
|
+
- **State B — directory exists and \`docs/features/<slug>/OPEN_QUESTIONS.md\` has at least one entry under \`## Pending Answers\` whose \`**Answer:**\` line is still \`_(fill in)_\`.** Print:
|
|
56
|
+
|
|
57
|
+
\`\`\`
|
|
58
|
+
Feature kickoff blocked — waiting on answers in docs/features/<slug>/OPEN_QUESTIONS.md.
|
|
59
|
+
|
|
60
|
+
Unanswered: #<n>, #<n>
|
|
61
|
+
Answered: #<n>
|
|
62
|
+
|
|
63
|
+
Fill in the remaining **Answer:** lines and re-run feature-kickoff.
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
Then stop. Do not edit the roadmap or phase files.
|
|
67
|
+
|
|
68
|
+
- **State C — directory exists, no pending answers (refresh).** Re-read the
|
|
69
|
+
existing roadmap and phases. Reconcile with the operator's note in the session
|
|
70
|
+
header (if it adds new scope or corrects something), then update files in place.
|
|
71
|
+
Skip to Step 4.
|
|
72
|
+
|
|
73
|
+
## Step 3 — Decompose the feature into phases
|
|
74
|
+
|
|
75
|
+
Plan 3–6 phases. Each phase has 2–6 concrete steps. Phases run sequentially.
|
|
76
|
+
Each phase must have a clear "done" definition.
|
|
77
|
+
|
|
78
|
+
Default phase shape (adjust per feature; collapse to 2–3 phases if small,
|
|
79
|
+
expand if large):
|
|
80
|
+
|
|
81
|
+
1. **Discovery** — read existing code, find integration points, list
|
|
82
|
+
constraints
|
|
83
|
+
2. **Design** — data model / API contract / UI shape decisions; capture in
|
|
84
|
+
DECISIONS where non-trivial
|
|
85
|
+
3. **Implementation** — break into commit-sized steps; each step touches
|
|
86
|
+
named files
|
|
87
|
+
4. **Testing** — unit / integration / manual coverage
|
|
88
|
+
5. **Rollout** — feature flag, docs, telemetry, deprecations
|
|
89
|
+
|
|
90
|
+
Each step is one imperative-voice action plus the file(s) or surface it
|
|
91
|
+
touches. Steps must be small enough to ship in one pair-program session.
|
|
92
|
+
|
|
93
|
+
**Don't pad.** A 4-step feature is fine. A 30-step feature with 3 phases of
|
|
94
|
+
filler is not.
|
|
95
|
+
|
|
96
|
+
## Step 4 — Capture genuine open questions only
|
|
97
|
+
|
|
98
|
+
If something truly cannot be decided autonomously (missing API decision,
|
|
99
|
+
ambiguous scope, conflicting existing patterns), append it to
|
|
100
|
+
\`docs/features/<slug>/OPEN_QUESTIONS.md\` under \`## Pending Answers\` using
|
|
101
|
+
this shape:
|
|
102
|
+
|
|
103
|
+
\`\`\`markdown
|
|
104
|
+
### N. <question>
|
|
105
|
+
*Phase: <phase number>. Raised: <today's ISO date>.*
|
|
106
|
+
**Answer:** _(fill in)_
|
|
107
|
+
\`\`\`
|
|
108
|
+
|
|
109
|
+
**Be sparing.** Most "questions" are decisions you should make and capture in
|
|
110
|
+
the feature's DECISIONS section. If you write more than 3 pending questions
|
|
111
|
+
you are over-using this.
|
|
112
|
+
|
|
113
|
+
If you write any pending questions, you are now in a partial State A — write
|
|
114
|
+
OPEN_QUESTIONS.md, write a stub manifest.json with \`"status": "blocked"\` at
|
|
115
|
+
the feature level, do NOT write ROADMAP.md / PHASES.md / STATUS.md, and stop
|
|
116
|
+
with a short note pointing the operator at OPEN_QUESTIONS.md. The next run
|
|
117
|
+
(after answers are filled) will pick up at State C and write the rest.
|
|
118
|
+
|
|
119
|
+
## Step 5 — Write files
|
|
120
|
+
|
|
121
|
+
All paths are relative to the workspace root. Create \`docs/features/<slug>/\`
|
|
122
|
+
if missing.
|
|
123
|
+
|
|
124
|
+
### \`docs/features/<slug>/ROADMAP.md\`
|
|
125
|
+
|
|
126
|
+
\`\`\`markdown
|
|
127
|
+
---
|
|
128
|
+
feature_slug: <slug>
|
|
129
|
+
feature_title: <descriptive title>
|
|
130
|
+
parent_task_id: <from session header; null if not provided>
|
|
131
|
+
workspace_id: <from session header; null if not provided>
|
|
132
|
+
user_id: <from session header; null if not provided>
|
|
133
|
+
created_at: <ISO date>
|
|
134
|
+
status: planning
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
# Feature Roadmap: <title>
|
|
138
|
+
|
|
139
|
+
## Summary
|
|
140
|
+
<2–3 sentence summary of what this feature does and why it matters>
|
|
141
|
+
|
|
142
|
+
## Phases
|
|
143
|
+
1. **<Phase 1 title>** — <one-line goal>
|
|
144
|
+
2. **<Phase 2 title>** — <one-line goal>
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
## Definition of done
|
|
148
|
+
- <criterion>
|
|
149
|
+
- <criterion>
|
|
150
|
+
|
|
151
|
+
## Notes
|
|
152
|
+
<anything important the operator should know — risks, constraints,
|
|
153
|
+
discovered-during-planning facts. "None." if none.>
|
|
154
|
+
\`\`\`
|
|
155
|
+
|
|
156
|
+
### \`docs/features/<slug>/PHASES.md\`
|
|
157
|
+
|
|
158
|
+
\`\`\`markdown
|
|
159
|
+
---
|
|
160
|
+
feature_slug: <slug>
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
# <title> — Phases
|
|
164
|
+
|
|
165
|
+
## Phase 1: <title>
|
|
166
|
+
**Goal:** <one sentence>
|
|
167
|
+
**Done when:** <crisp criterion>
|
|
168
|
+
|
|
169
|
+
- [ ] 1.1 — <action> *(touches: <file or surface>)*
|
|
170
|
+
- [ ] 1.2 — ...
|
|
171
|
+
|
|
172
|
+
## Phase 2: <title>
|
|
173
|
+
**Goal:** ...
|
|
174
|
+
**Done when:** ...
|
|
175
|
+
|
|
176
|
+
- [ ] 2.1 — ...
|
|
177
|
+
...
|
|
178
|
+
\`\`\`
|
|
179
|
+
|
|
180
|
+
### \`docs/features/<slug>/STATUS.md\`
|
|
181
|
+
|
|
182
|
+
\`\`\`markdown
|
|
183
|
+
---
|
|
184
|
+
feature_slug: <slug>
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# <title> — Status
|
|
188
|
+
|
|
189
|
+
_Last updated: <ISO date>_
|
|
190
|
+
_Current phase: 1_
|
|
191
|
+
|
|
192
|
+
## Progress
|
|
193
|
+
- [ ] Phase 1: <title>
|
|
194
|
+
- [ ] Phase 2: <title>
|
|
195
|
+
...
|
|
196
|
+
|
|
197
|
+
## Active step
|
|
198
|
+
None yet — kicked off, awaiting first work session.
|
|
199
|
+
|
|
200
|
+
## Recent activity
|
|
201
|
+
- <ISO date>: feature kicked off
|
|
202
|
+
|
|
203
|
+
## Notes
|
|
204
|
+
None.
|
|
205
|
+
\`\`\`
|
|
206
|
+
|
|
207
|
+
### \`docs/features/<slug>/manifest.json\`
|
|
208
|
+
|
|
209
|
+
\`\`\`json
|
|
210
|
+
{
|
|
211
|
+
"feature_slug": "<slug>",
|
|
212
|
+
"feature_title": "<title>",
|
|
213
|
+
"parent_task_id": "<id or null>",
|
|
214
|
+
"workspace_id": "<id or null>",
|
|
215
|
+
"user_id": "<id or null>",
|
|
216
|
+
"product_slug": "<from session header or null>",
|
|
217
|
+
"project_slug": "<from session header or null>",
|
|
218
|
+
"kicked_off_at": "<ISO timestamp>",
|
|
219
|
+
"status": "planning",
|
|
220
|
+
"current_phase": "phase-1",
|
|
221
|
+
"phases": [
|
|
222
|
+
{
|
|
223
|
+
"id": "phase-1",
|
|
224
|
+
"title": "<title>",
|
|
225
|
+
"goal": "<goal>",
|
|
226
|
+
"done_when": "<criterion>",
|
|
227
|
+
"status": "pending",
|
|
228
|
+
"steps": [
|
|
229
|
+
{
|
|
230
|
+
"id": "phase-1-step-1",
|
|
231
|
+
"action": "<imperative action>",
|
|
232
|
+
"touches": "<file or surface>",
|
|
233
|
+
"status": "pending"
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"definition_of_done": ["<criterion>", "<criterion>"]
|
|
239
|
+
}
|
|
240
|
+
\`\`\`
|
|
241
|
+
|
|
242
|
+
The \`manifest.json\` is the canonical machine-readable form. The MD files are
|
|
243
|
+
the human-readable view. Keep them consistent — if you change a phase title or
|
|
244
|
+
step in one, change it in all four files.
|
|
245
|
+
|
|
246
|
+
## Step 6 — Append to project state (don't overwrite)
|
|
247
|
+
|
|
248
|
+
Touch two project-level files. **Append only — do not overwrite other
|
|
249
|
+
sections.**
|
|
250
|
+
|
|
251
|
+
\`docs/state/STATUS.md\` — under \`## Notes\` (create the section if missing),
|
|
252
|
+
append:
|
|
253
|
+
|
|
254
|
+
> Feature kicked off: \`<slug>\` — see \`docs/features/<slug>/\`.
|
|
255
|
+
|
|
256
|
+
\`docs/state/CHANGELOG.md\` — under \`## [Unreleased]\` → \`### Added\`,
|
|
257
|
+
append a single bullet:
|
|
258
|
+
|
|
259
|
+
> Feature roadmap: \`<slug>\` (kickoff <date>).
|
|
260
|
+
|
|
261
|
+
Do not touch \`docs/state/HANDOFF.md\`, \`docs/state/DECISIONS.md\`,
|
|
262
|
+
\`docs/state/OPEN_QUESTIONS.md\`, or any application code. Feature
|
|
263
|
+
kickoff is planning only.
|
|
264
|
+
|
|
265
|
+
## Step 7 — Output for the operator
|
|
266
|
+
|
|
267
|
+
End your response with a copyable block summarizing what was written:
|
|
268
|
+
|
|
269
|
+
\`\`\`
|
|
270
|
+
Feature kickoff complete: <slug>
|
|
271
|
+
|
|
272
|
+
Files:
|
|
273
|
+
docs/features/<slug>/ROADMAP.md
|
|
274
|
+
docs/features/<slug>/PHASES.md
|
|
275
|
+
docs/features/<slug>/STATUS.md
|
|
276
|
+
docs/features/<slug>/manifest.json
|
|
277
|
+
|
|
278
|
+
Project state:
|
|
279
|
+
docs/state/STATUS.md (appended Notes entry)
|
|
280
|
+
docs/state/CHANGELOG.md (appended Added entry)
|
|
281
|
+
|
|
282
|
+
Next: pick a step from PHASES.md and start a normal pair-program session on it.
|
|
283
|
+
The dashboard will track progress against manifest.json.
|
|
284
|
+
\`\`\`
|
|
285
|
+
|
|
286
|
+
## Non-negotiables
|
|
287
|
+
|
|
288
|
+
- Write files. Do not describe what you would write.
|
|
289
|
+
- Use the operator's task description as the feature description verbatim
|
|
290
|
+
where reasonable; only paraphrase to fit slug/title/summary length.
|
|
291
|
+
- Keep \`manifest.json\` consistent with the three MD files at all times.
|
|
292
|
+
- One feature per run. The slug picked in Step 1 is final for this run.
|
|
293
|
+
- Do not modify project code. Planning only.
|
|
294
|
+
- Never overwrite an existing \`docs/features/<slug>/ROADMAP.md\` outside
|
|
295
|
+
State C. If State A detection misfires (directory exists but you think it
|
|
296
|
+
shouldn't), halt with a one-line note rather than clobbering.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
`;
|
|
301
|
+
|
|
302
|
+
function buildFeatureKickoffPrompt({
|
|
303
|
+
taskDescription,
|
|
304
|
+
productSlug,
|
|
305
|
+
projectSlug,
|
|
306
|
+
workspace,
|
|
307
|
+
userId,
|
|
308
|
+
workspaceId,
|
|
309
|
+
parentTaskId
|
|
310
|
+
} = {}) {
|
|
311
|
+
const header = ['Mode: feature-kickoff'];
|
|
312
|
+
if (productSlug) header.push(`Product: ${productSlug}`);
|
|
313
|
+
if (projectSlug) header.push(`Project: ${projectSlug}`);
|
|
314
|
+
if (workspace) header.push(`Workspace: ${workspace}`);
|
|
315
|
+
if (userId) header.push(`User ID: ${userId}`);
|
|
316
|
+
if (workspaceId) header.push(`Workspace ID: ${workspaceId}`);
|
|
317
|
+
if (parentTaskId) header.push(`Parent task ID: ${parentTaskId}`);
|
|
318
|
+
if (taskDescription) header.push(`Feature description (use as-is for the title and summary):\n${taskDescription}`);
|
|
319
|
+
const ctx = `Session context:\n${header.join('\n')}\n\n`;
|
|
320
|
+
return ctx + FEATURE_KICKOFF_BODY;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = {
|
|
324
|
+
title: 'Feature Kickoff',
|
|
325
|
+
body: FEATURE_KICKOFF_BODY,
|
|
326
|
+
buildPrompt: buildFeatureKickoffPrompt
|
|
327
|
+
};
|
package/lib/templates/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const kickoff = require('./kickoff');
|
|
4
|
+
const featureKickoff = require('./feature-kickoff');
|
|
4
5
|
const resume = require('./resume');
|
|
5
6
|
const sessionEnd = require('./session-end');
|
|
6
7
|
const recovery = require('./recovery');
|
|
@@ -8,6 +9,7 @@ const codingRuleset = require('./coding-ruleset');
|
|
|
8
9
|
|
|
9
10
|
const MODES = {
|
|
10
11
|
kickoff,
|
|
12
|
+
'feature-kickoff': featureKickoff,
|
|
11
13
|
resume,
|
|
12
14
|
'session-end': sessionEnd,
|
|
13
15
|
recovery
|