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.
- package/lib/knoxis-local-agent.js +8 -8
- package/lib/session-recorder.js +92 -18
- package/package.json +1 -1
|
@@ -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
|
-
//
|
|
599
|
-
//
|
|
600
|
-
//
|
|
601
|
-
// --session-id`, which gives Claude proper tool
|
|
602
|
-
// to `claude` without -p and silently
|
|
603
|
-
//
|
|
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)}"`;
|
package/lib/session-recorder.js
CHANGED
|
@@ -34,39 +34,106 @@ function extractFilesTouched(diff) {
|
|
|
34
34
|
return Array.from(seen);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Snapshot the
|
|
38
|
-
//
|
|
39
|
-
//
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
274
|
-
// display the latest STATUS / HANDOFF /
|
|
275
|
-
// needing live filesystem access
|
|
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
|
};
|