knoxis-helper 1.8.1 → 1.8.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.
@@ -595,14 +595,14 @@ function resolvePairProgramScript() {
595
595
  }
596
596
 
597
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
+ // Every kit mode rewrites files on disk STATUS / HANDOFF / DECISIONS for
599
+ // session-end, the docs/state/* + docs/features/<slug>/* bag for kickoff /
600
+ // feature-kickoff / resume, and recovery's triage notes. The interactive
601
+ // runner invokes `claude -p --session-id`, which gives Claude proper tool
602
+ // access; the older pair-program.js pipes to `claude` without -p and silently
603
+ // skips the file writes. Route every kit mode through the interactive runner
604
+ // so their kit prompts actually do their work.
605
+ const KIT_MODES_VIA_INTERACTIVE = new Set(['kickoff', 'feature-kickoff', 'resume', 'session-end', 'recovery']);
606
606
 
607
607
  function buildPairProgramCommand({ scriptPath, workspace, mode, archetype, pattern, productSlug, projectSlug, taskPrompt, userId, workspaceId, taskIds }) {
608
608
  const q = v => `"${escapeForDoubleQuotedShellArg(v)}"`;
@@ -34,39 +34,106 @@ function extractFilesTouched(diff) {
34
34
  return Array.from(seen);
35
35
  }
36
36
 
37
- // Snapshot the 6 well-known state files (docs/state/*.md) so the
38
- // session record carries the latest STATUS / HANDOFF / DECISIONS / etc.
39
- // up to the portal for display on qig.ai.
37
+ // Snapshot the canonical state files so the session record carries the
38
+ // latest STATUS / HANDOFF / DECISIONS / etc. up to the portal for display
39
+ // on qig.ai. Three groups are captured into a single bag keyed by relative
40
+ // path; the frontend distinguishes them by path prefix.
41
+ //
42
+ // docs/state/*.md — project-level state (always present after scaffold)
43
+ // docs/architecture/*.md — ARCHITECTURE.md + ADRs from project-kickoff
44
+ // docs/features/<slug>/* — per-feature ROADMAP / PHASES / STATUS / manifest
45
+ // from feature-kickoff (one entry per feature)
46
+ //
47
+ // All three flow through `stateFiles` on the session record. The Java
48
+ // PairProgrammerSessionService persists the bag to the `state_files` JSONB
49
+ // column; no schema change is needed to add new path prefixes.
40
50
  const STATE_FILES_TO_CAPTURE = [
41
51
  'docs/state/STATUS.md',
42
52
  'docs/state/HANDOFF.md',
43
53
  'docs/state/DECISIONS.md',
44
54
  'docs/state/CHANGELOG.md',
45
55
  'docs/state/OPEN_QUESTIONS.md',
46
- 'docs/state/RISKS.md'
56
+ 'docs/state/RISKS.md',
57
+ // Project architecture pack from kickoff v7.
58
+ 'docs/architecture/ARCHITECTURE.md'
59
+ ];
60
+ const FEATURE_FILE_NAMES = [
61
+ 'ROADMAP.md',
62
+ 'PHASES.md',
63
+ 'STATUS.md',
64
+ 'OPEN_QUESTIONS.md',
65
+ 'manifest.json'
47
66
  ];
48
67
  const MAX_STATE_FILE_BYTES = 256 * 1024; // 256KB per file
49
68
 
69
+ function readSafe(absPath, relPath, out) {
70
+ try {
71
+ const stat = fs.statSync(absPath);
72
+ if (!stat.isFile()) return;
73
+ if (stat.size > MAX_STATE_FILE_BYTES) {
74
+ out[relPath] = `[File omitted: ${stat.size} bytes exceeds ${MAX_STATE_FILE_BYTES}-byte cap]`;
75
+ return;
76
+ }
77
+ out[relPath] = fs.readFileSync(absPath, 'utf8');
78
+ } catch (e) {
79
+ // Missing file is normal — skip silently.
80
+ }
81
+ }
82
+
50
83
  function readStateFiles(workspace) {
51
84
  const out = {};
52
85
  if (!workspace) return out;
86
+
87
+ // Fixed list (state pack + ARCHITECTURE).
53
88
  for (const rel of STATE_FILES_TO_CAPTURE) {
54
- const abs = path.join(workspace, rel);
55
- try {
56
- const stat = fs.statSync(abs);
57
- if (!stat.isFile()) continue;
58
- if (stat.size > MAX_STATE_FILE_BYTES) {
59
- out[rel] = `[File omitted: ${stat.size} bytes exceeds ${MAX_STATE_FILE_BYTES}-byte cap]`;
60
- continue;
89
+ readSafe(path.join(workspace, rel), rel, out);
90
+ }
91
+
92
+ // ADRs — docs/architecture/adr/*.md.
93
+ const adrDir = path.join(workspace, 'docs', 'architecture', 'adr');
94
+ try {
95
+ for (const entry of fs.readdirSync(adrDir, { withFileTypes: true })) {
96
+ if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
97
+ const rel = `docs/architecture/adr/${entry.name}`;
98
+ readSafe(path.join(adrDir, entry.name), rel, out);
99
+ }
100
+ } catch (e) {
101
+ // No ADR dir is normal.
102
+ }
103
+
104
+ // Per-feature files — docs/features/<slug>/{ROADMAP,PHASES,STATUS,OPEN_QUESTIONS}.md
105
+ // and manifest.json. One slug = one folder.
106
+ const featuresDir = path.join(workspace, 'docs', 'features');
107
+ try {
108
+ for (const entry of fs.readdirSync(featuresDir, { withFileTypes: true })) {
109
+ if (!entry.isDirectory()) continue;
110
+ for (const filename of FEATURE_FILE_NAMES) {
111
+ const rel = `docs/features/${entry.name}/${filename}`;
112
+ readSafe(path.join(featuresDir, entry.name, filename), rel, out);
61
113
  }
62
- out[rel] = fs.readFileSync(abs, 'utf8');
63
- } catch (e) {
64
- // Missing file is normal — skip silently.
65
114
  }
115
+ } catch (e) {
116
+ // No features dir is normal.
66
117
  }
118
+
67
119
  return out;
68
120
  }
69
121
 
122
+ // Enumerate feature slugs from the workspace so the frontend can list features
123
+ // without parsing the bag. Returns sorted array of slug strings.
124
+ function listFeatureSlugs(workspace) {
125
+ if (!workspace) return [];
126
+ const featuresDir = path.join(workspace, 'docs', 'features');
127
+ try {
128
+ return fs.readdirSync(featuresDir, { withFileTypes: true })
129
+ .filter(e => e.isDirectory())
130
+ .map(e => e.name)
131
+ .sort();
132
+ } catch (e) {
133
+ return [];
134
+ }
135
+ }
136
+
70
137
  // Snapshot product.local.json from the project's parent directory (per
71
138
  // kickoff v7 Phase -1) so the session record carries product context up to
72
139
  // the portal. Multiple projects under one product share this file; the
@@ -270,10 +337,16 @@ class SessionRecorder {
270
337
  completedSteps,
271
338
  closedCleanly,
272
339
  filesTouched: extractFilesTouched(totalDiff),
273
- // Snapshot of docs/state/*.md at session-end so the QIG frontend can
274
- // display the latest STATUS / HANDOFF / DECISIONS / etc. without
275
- // needing live filesystem access on the dev's machine.
340
+ // Snapshot of state-pack + architecture + feature files at session-end
341
+ // so the QIG frontend can display the latest STATUS / HANDOFF /
342
+ // DECISIONS / ROADMAP / etc. without needing live filesystem access
343
+ // on the dev's machine. Single bag keyed by relative path; frontend
344
+ // filters by prefix (docs/state/*, docs/architecture/*,
345
+ // docs/features/<slug>/*).
276
346
  stateFiles: readStateFiles(this.workspace),
347
+ // Quick index of feature slugs found in docs/features/. Lets the
348
+ // frontend enumerate features without parsing the bag.
349
+ featureSlugs: listFeatureSlugs(this.workspace),
277
350
  // Snapshot of product.local.json from the parent directory so the
278
351
  // frontend can render product context alongside the session record.
279
352
  productContext: readProductContext(this.workspace),
@@ -299,5 +372,6 @@ module.exports = {
299
372
  slugify,
300
373
  extractFilesTouched,
301
374
  readStateFiles,
302
- readProductContext
375
+ readProductContext,
376
+ listFeatureSlugs
303
377
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "knoxis-helper",
3
- "version": "1.8.1",
3
+ "version": "1.8.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"