knoxis-helper 1.5.2 → 1.6.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/session-recorder.js +45 -3
- package/lib/state-scaffold.js +10 -3
- package/package.json +1 -1
package/lib/session-recorder.js
CHANGED
|
@@ -32,10 +32,45 @@ function extractFilesTouched(diff) {
|
|
|
32
32
|
return Array.from(seen);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// Snapshot the 6 well-known state files (docs/state/*.md) so the
|
|
36
|
+
// session record carries the latest STATUS / HANDOFF / DECISIONS / etc.
|
|
37
|
+
// up to the portal for display on qig.ai.
|
|
38
|
+
const STATE_FILES_TO_CAPTURE = [
|
|
39
|
+
'docs/state/STATUS.md',
|
|
40
|
+
'docs/state/HANDOFF.md',
|
|
41
|
+
'docs/state/DECISIONS.md',
|
|
42
|
+
'docs/state/CHANGELOG.md',
|
|
43
|
+
'docs/state/OPEN_QUESTIONS.md',
|
|
44
|
+
'docs/state/RISKS.md'
|
|
45
|
+
];
|
|
46
|
+
const MAX_STATE_FILE_BYTES = 256 * 1024; // 256KB per file
|
|
47
|
+
|
|
48
|
+
function readStateFiles(workspace) {
|
|
49
|
+
const out = {};
|
|
50
|
+
if (!workspace) return out;
|
|
51
|
+
for (const rel of STATE_FILES_TO_CAPTURE) {
|
|
52
|
+
const abs = path.join(workspace, rel);
|
|
53
|
+
try {
|
|
54
|
+
const stat = fs.statSync(abs);
|
|
55
|
+
if (!stat.isFile()) continue;
|
|
56
|
+
if (stat.size > MAX_STATE_FILE_BYTES) {
|
|
57
|
+
out[rel] = `[File omitted: ${stat.size} bytes exceeds ${MAX_STATE_FILE_BYTES}-byte cap]`;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
out[rel] = fs.readFileSync(abs, 'utf8');
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Missing file is normal — skip silently.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Default git-call helpers — return null on any failure (callers want a string
|
|
69
|
+
// or null). stdio: 'pipe' prevents stderr from leaking to the parent process
|
|
70
|
+
// (e.g. "fatal: Needed a single revision" when the repo has no commits).
|
|
36
71
|
function defaultSafeExec(cmd, cwd) {
|
|
37
72
|
try {
|
|
38
|
-
return execSync(cmd, { cwd, encoding: 'utf8', timeout: 30000 }).trim();
|
|
73
|
+
return execSync(cmd, { cwd, encoding: 'utf8', timeout: 30000, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
39
74
|
} catch (e) {
|
|
40
75
|
return null;
|
|
41
76
|
}
|
|
@@ -44,7 +79,10 @@ function defaultSafeExec(cmd, cwd) {
|
|
|
44
79
|
const execAsync = util.promisify(exec);
|
|
45
80
|
async function defaultSafeExecAsync(cmd, cwd) {
|
|
46
81
|
try {
|
|
47
|
-
|
|
82
|
+
// exec() inherits stderr by default; redirect to /dev/null at the shell
|
|
83
|
+
// level so failures (no commits, missing repo) don't pollute output.
|
|
84
|
+
const safeCmd = process.platform === 'win32' ? `${cmd} 2>nul` : `${cmd} 2>/dev/null`;
|
|
85
|
+
const { stdout } = await execAsync(safeCmd, { cwd, encoding: 'utf8', timeout: 30000, maxBuffer: 10 * 1024 * 1024 });
|
|
48
86
|
return stdout.trim();
|
|
49
87
|
} catch (e) {
|
|
50
88
|
return null;
|
|
@@ -204,6 +242,10 @@ class SessionRecorder {
|
|
|
204
242
|
completedSteps,
|
|
205
243
|
closedCleanly,
|
|
206
244
|
filesTouched: extractFilesTouched(totalDiff),
|
|
245
|
+
// Snapshot of docs/state/*.md at session-end so the QIG frontend can
|
|
246
|
+
// display the latest STATUS / HANDOFF / DECISIONS / etc. without
|
|
247
|
+
// needing live filesystem access on the dev's machine.
|
|
248
|
+
stateFiles: readStateFiles(this.workspace),
|
|
207
249
|
decisionsLogged: [],
|
|
208
250
|
waiversRequested: [],
|
|
209
251
|
incidentsFlagged: [],
|
package/lib/state-scaffold.js
CHANGED
|
@@ -19,13 +19,17 @@ const STATE_LAYOUT = {
|
|
|
19
19
|
overwrite: false
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
|
+
// Empty sections so the digest parser returns []s instead of inheriting
|
|
23
|
+
// scaffold copy. Claude populates Done / In flight / Next / Notes during
|
|
24
|
+
// session-end per the standards in the systemIntro.
|
|
22
25
|
path: 'docs/state/STATUS.md',
|
|
23
|
-
content: () => `# Status\n\n_Last updated: ${today()}_\n\n## Done\n
|
|
26
|
+
content: () => `# Status\n\n_Last updated: ${today()}_\n\n## Done\n\n## In flight\n\n## Next\n\n## Notes\n`,
|
|
24
27
|
overwrite: false
|
|
25
28
|
},
|
|
26
29
|
{
|
|
30
|
+
// Empty placeholder. HANDOFF gets overwritten in full at every session-end.
|
|
27
31
|
path: 'docs/state/HANDOFF.md',
|
|
28
|
-
content: () => `# Handoff\n\
|
|
32
|
+
content: () => `# Handoff\n\n_No prior session — this file is overwritten at session-end._\n\n## Where I stopped\n\n## Why I stopped here\n\n## First thing to do next session\n\n## Landmines / gotchas\n\n## Environment state\n`,
|
|
29
33
|
overwrite: false
|
|
30
34
|
},
|
|
31
35
|
{
|
|
@@ -34,8 +38,11 @@ const STATE_LAYOUT = {
|
|
|
34
38
|
overwrite: false
|
|
35
39
|
},
|
|
36
40
|
{
|
|
41
|
+
// Empty Unreleased block — Claude appends real entries at session-end
|
|
42
|
+
// when code actually shipped. No fake "scaffolded" entry that would
|
|
43
|
+
// surface as a real changelog item on the dashboard.
|
|
37
44
|
path: 'docs/state/CHANGELOG.md',
|
|
38
|
-
content: () => `# Changelog\n\nAll notable changes to this project will be documented in this file.\nFormat: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [Unreleased]\n\n### Added\n
|
|
45
|
+
content: () => `# Changelog\n\nAll notable changes to this project will be documented in this file.\nFormat: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).\n\n## [Unreleased]\n\n### Added\n\n### Changed\n\n### Fixed\n\n### Removed\n`,
|
|
39
46
|
overwrite: false
|
|
40
47
|
},
|
|
41
48
|
{
|