gsd-pi 2.77.0-dev.1d17f366c → 2.77.0-dev.58d3d4d6c
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/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-post-unit.js +79 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +48 -7
- package/dist/resources/extensions/gsd/auto-start.js +62 -3
- package/dist/resources/extensions/gsd/auto.js +34 -0
- package/dist/resources/extensions/gsd/context-store.js +23 -7
- package/dist/resources/extensions/gsd/forensics.js +106 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
- package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
- package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
- package/dist/resources/extensions/gsd/worktree-manager.js +51 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
- package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/session.ts +7 -0
- package/src/resources/extensions/gsd/auto-post-unit.ts +81 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +59 -7
- package/src/resources/extensions/gsd/auto-start.ts +64 -2
- package/src/resources/extensions/gsd/auto.ts +37 -0
- package/src/resources/extensions/gsd/context-store.ts +25 -8
- package/src/resources/extensions/gsd/forensics.ts +118 -1
- package/src/resources/extensions/gsd/git-service.ts +16 -0
- package/src/resources/extensions/gsd/journal.ts +11 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
- package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
- package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
- package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +5 -8
- package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +12 -9
- package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
- package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/forensics-worktree-telemetry.test.ts +145 -0
- package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +6 -1
- package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
- package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +10 -3
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
- package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
- package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
- package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
- package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
- package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/test-helpers.test.ts +147 -0
- package/src/resources/extensions/gsd/tests/test-helpers.ts +140 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
- package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +3 -3
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +53 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
- package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
- /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → Cev5xrAYA3ZGTRLyjR2fX}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vidAVJkURvTJ0_V2-64ro → Cev5xrAYA3ZGTRLyjR2fX}/_ssgManifest.js +0 -0
|
@@ -27,6 +27,7 @@ import { readFileSync } from "node:fs";
|
|
|
27
27
|
import { join } from "node:path";
|
|
28
28
|
|
|
29
29
|
import { buildFlatRateContext } from "../auto-model-selection.ts";
|
|
30
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
30
31
|
|
|
31
32
|
// ─── Bug 2: this-binding regression ─────────────────────────────────────
|
|
32
33
|
|
|
@@ -69,7 +70,7 @@ test("isSamePath short-circuits ENOENT before logging a warning", () => {
|
|
|
69
70
|
assert.ok(fnIdx !== -1, "isSamePath function exists");
|
|
70
71
|
|
|
71
72
|
// Grab the function body (enough to cover the catch block).
|
|
72
|
-
const fnBody = src
|
|
73
|
+
const fnBody = extractSourceRegion(src, "function isSamePath", { fromIdx: fnIdx });
|
|
73
74
|
|
|
74
75
|
const catchIdx = fnBody.indexOf("catch");
|
|
75
76
|
assert.ok(catchIdx !== -1, "isSamePath has a catch block");
|
|
@@ -103,7 +104,7 @@ test("checkAutoStartAfterDiscuss guards DISCUSSION-MANIFEST.json unlink with exi
|
|
|
103
104
|
|
|
104
105
|
// Everything from the comment to a short distance below should contain
|
|
105
106
|
// the existsSync guard before the unlinkSync call.
|
|
106
|
-
const block = src
|
|
107
|
+
const block = extractSourceRegion(src, "remove discussion manifest after auto-start", { fromIdx: cleanupIdx });
|
|
107
108
|
|
|
108
109
|
const existsIdx = block.indexOf("existsSync(manifestPath)");
|
|
109
110
|
const unlinkIdx = block.indexOf("unlinkSync(manifestPath)");
|
|
@@ -2,6 +2,7 @@ import { describe, test } from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
5
6
|
|
|
6
7
|
const systemContextSrc = readFileSync(
|
|
7
8
|
join(import.meta.dirname, "..", "bootstrap", "system-context.ts"),
|
|
@@ -29,7 +30,7 @@ describe("bootstrap deriveState DB guards (#3844)", () => {
|
|
|
29
30
|
test("register-hooks opens DB before deriveState in session_before_compact", () => {
|
|
30
31
|
const compactIdx = registerHooksSrc.indexOf('pi.on("session_before_compact"');
|
|
31
32
|
assert.ok(compactIdx > -1, "register-hooks should define session_before_compact");
|
|
32
|
-
const compactSection = registerHooksSrc.
|
|
33
|
+
const compactSection = extractSourceRegion(registerHooksSrc, 'pi.on("session_before_compact"');
|
|
33
34
|
const ensureIdx = compactSection.indexOf("ensureDbOpen()");
|
|
34
35
|
const deriveIdx = compactSection.indexOf("deriveState(basePath)");
|
|
35
36
|
assert.ok(ensureIdx > -1, "session_before_compact should call ensureDbOpen()");
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resolveCanonicalMilestoneRoot — the worktree-aware reader
|
|
3
|
+
* that fixes #4761 (worktree work stranded when auto-loop exits without
|
|
4
|
+
* milestone completion).
|
|
5
|
+
*
|
|
6
|
+
* Contract: given (basePath, milestoneId), return the worktree path if a
|
|
7
|
+
* live git worktree exists for that milestone at .gsd/worktrees/<MID>/;
|
|
8
|
+
* otherwise return basePath unchanged. A live worktree has a .git file
|
|
9
|
+
* (not directory) — a bare directory without .git is a stale leftover.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import test from "node:test";
|
|
13
|
+
import assert from "node:assert/strict";
|
|
14
|
+
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
|
|
19
|
+
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.ts";
|
|
20
|
+
|
|
21
|
+
function makeTmpBase(): string {
|
|
22
|
+
const base = join(tmpdir(), `gsd-canon-test-${randomUUID()}`);
|
|
23
|
+
mkdirSync(join(base, ".gsd", "milestones"), { recursive: true });
|
|
24
|
+
return base;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function cleanup(base: string): void {
|
|
28
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a worktree directory shape that looks live: the .gsd/worktrees/<MID>/
|
|
33
|
+
* directory with a .git file containing a gitdir: pointer. We don't need a
|
|
34
|
+
* real git worktree — the resolver only checks for the .git file's presence.
|
|
35
|
+
*/
|
|
36
|
+
function makeLiveWorktree(base: string, mid: string): string {
|
|
37
|
+
const wtPath = join(base, ".gsd", "worktrees", mid);
|
|
38
|
+
mkdirSync(wtPath, { recursive: true });
|
|
39
|
+
writeFileSync(
|
|
40
|
+
join(wtPath, ".git"),
|
|
41
|
+
`gitdir: ${join(base, ".git", "worktrees", mid)}\n`,
|
|
42
|
+
);
|
|
43
|
+
return wtPath;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function makeStaleWorktree(base: string, mid: string): string {
|
|
47
|
+
const wtPath = join(base, ".gsd", "worktrees", mid);
|
|
48
|
+
mkdirSync(wtPath, { recursive: true });
|
|
49
|
+
// No .git file — this is the stale-leftover shape createWorktree() sees
|
|
50
|
+
// and cleans up.
|
|
51
|
+
return wtPath;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test("returns worktree path when a live worktree exists for the milestone", () => {
|
|
55
|
+
const base = makeTmpBase();
|
|
56
|
+
try {
|
|
57
|
+
const wtPath = makeLiveWorktree(base, "M001");
|
|
58
|
+
const result = resolveCanonicalMilestoneRoot(base, "M001");
|
|
59
|
+
assert.equal(result, wtPath);
|
|
60
|
+
} finally {
|
|
61
|
+
cleanup(base);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("returns basePath when no worktree directory exists", () => {
|
|
66
|
+
const base = makeTmpBase();
|
|
67
|
+
try {
|
|
68
|
+
const result = resolveCanonicalMilestoneRoot(base, "M001");
|
|
69
|
+
assert.equal(result, base);
|
|
70
|
+
} finally {
|
|
71
|
+
cleanup(base);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("returns basePath when worktree directory exists but has no .git file (stale)", () => {
|
|
76
|
+
const base = makeTmpBase();
|
|
77
|
+
try {
|
|
78
|
+
makeStaleWorktree(base, "M001");
|
|
79
|
+
const result = resolveCanonicalMilestoneRoot(base, "M001");
|
|
80
|
+
assert.equal(result, base);
|
|
81
|
+
} finally {
|
|
82
|
+
cleanup(base);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns basePath for invalid milestoneId (path separators)", () => {
|
|
87
|
+
const base = makeTmpBase();
|
|
88
|
+
try {
|
|
89
|
+
// Even if a worktree coincidentally exists, the guard should reject.
|
|
90
|
+
assert.equal(resolveCanonicalMilestoneRoot(base, "../evil"), base);
|
|
91
|
+
assert.equal(resolveCanonicalMilestoneRoot(base, "M001/subdir"), base);
|
|
92
|
+
assert.equal(resolveCanonicalMilestoneRoot(base, "M001\\subdir"), base);
|
|
93
|
+
assert.equal(resolveCanonicalMilestoneRoot(base, ""), base);
|
|
94
|
+
} finally {
|
|
95
|
+
cleanup(base);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("only returns the worktree for the requested milestone, not siblings", () => {
|
|
100
|
+
const base = makeTmpBase();
|
|
101
|
+
try {
|
|
102
|
+
makeLiveWorktree(base, "M001");
|
|
103
|
+
const result = resolveCanonicalMilestoneRoot(base, "M002");
|
|
104
|
+
assert.equal(result, base, "M002 has no worktree → basePath");
|
|
105
|
+
} finally {
|
|
106
|
+
cleanup(base);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
@@ -11,6 +11,7 @@ import { describe, it } from 'node:test'
|
|
|
11
11
|
import assert from 'node:assert/strict'
|
|
12
12
|
import { readFileSync } from 'node:fs'
|
|
13
13
|
import { resolve } from 'node:path'
|
|
14
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
14
15
|
|
|
15
16
|
const src = readFileSync(
|
|
16
17
|
resolve(process.cwd(), 'src', 'resources', 'extensions', 'gsd', 'tools', 'complete-slice.ts'),
|
|
@@ -59,7 +60,7 @@ describe('complete-slice verification gate (#3580)', () => {
|
|
|
59
60
|
const gateIdx = src.indexOf('BLOCKED_SIGNALS.test(')
|
|
60
61
|
assert.ok(gateIdx !== -1)
|
|
61
62
|
|
|
62
|
-
const afterGate = src.
|
|
63
|
+
const afterGate = extractSourceRegion(src, 'BLOCKED_SIGNALS.test(')
|
|
63
64
|
assert.ok(
|
|
64
65
|
afterGate.includes('return { error:'),
|
|
65
66
|
'blocked signal detection must return an error',
|
|
@@ -627,4 +627,83 @@ Integration tests mock external services.
|
|
|
627
627
|
|
|
628
628
|
assert.strictEqual(result, '', 'empty content returns empty string');
|
|
629
629
|
});
|
|
630
|
+
|
|
631
|
+
// ── Regression: issue #4719 — single-H2 with many H3 entries ──────────────
|
|
632
|
+
// A KNOWLEDGE.md structured as one top-level H2 with many H3 entries must
|
|
633
|
+
// filter at H3 granularity; otherwise one keyword match against the H2
|
|
634
|
+
// header or first paragraph returns the entire file.
|
|
635
|
+
test("single H2 with many H3 entries filters at H3 level (issue #4719)", async () => {
|
|
636
|
+
const singleH2Knowledge = `# Project Knowledge
|
|
637
|
+
|
|
638
|
+
## Patterns
|
|
639
|
+
|
|
640
|
+
### Database: prepared statements
|
|
641
|
+
Always use prepared statements with SQLite.
|
|
642
|
+
|
|
643
|
+
### API: versioned paths
|
|
644
|
+
Use /v1/resource style versioning.
|
|
645
|
+
|
|
646
|
+
### Testing: node:test
|
|
647
|
+
Prefer node:test over external frameworks.
|
|
648
|
+
|
|
649
|
+
### Deployment: blue-green
|
|
650
|
+
Blue-green deployment for zero-downtime releases.
|
|
651
|
+
`;
|
|
652
|
+
|
|
653
|
+
const result = await queryKnowledge(singleH2Knowledge, ['database']);
|
|
654
|
+
|
|
655
|
+
// Should include only the matching H3 entry, not the whole file
|
|
656
|
+
assert.match(result, /Database: prepared statements/, 'includes matching H3 entry');
|
|
657
|
+
assert.ok(
|
|
658
|
+
!result.includes('API: versioned paths'),
|
|
659
|
+
'does not include non-matching H3 entry',
|
|
660
|
+
);
|
|
661
|
+
assert.ok(
|
|
662
|
+
!result.includes('Testing: node:test'),
|
|
663
|
+
'does not include non-matching H3 entry',
|
|
664
|
+
);
|
|
665
|
+
assert.ok(
|
|
666
|
+
!result.includes('Deployment: blue-green'),
|
|
667
|
+
'does not include non-matching H3 entry',
|
|
668
|
+
);
|
|
669
|
+
// The returned payload must be dramatically smaller than the full content
|
|
670
|
+
assert.ok(
|
|
671
|
+
result.length < singleH2Knowledge.length / 2,
|
|
672
|
+
`scoped result (${result.length} chars) should be <50% of full content (${singleH2Knowledge.length} chars)`,
|
|
673
|
+
);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
test("single H2 with H3 entries returns empty when no H3 matches (issue #4719)", async () => {
|
|
677
|
+
const singleH2Knowledge = `# Project Knowledge
|
|
678
|
+
|
|
679
|
+
## Patterns
|
|
680
|
+
|
|
681
|
+
### Database: prepared statements
|
|
682
|
+
Always use prepared statements with SQLite.
|
|
683
|
+
|
|
684
|
+
### API: versioned paths
|
|
685
|
+
Use /v1/resource style versioning.
|
|
686
|
+
`;
|
|
687
|
+
|
|
688
|
+
const result = await queryKnowledge(singleH2Knowledge, ['nonexistent']);
|
|
689
|
+
|
|
690
|
+
assert.strictEqual(result, '', 'no H3 match returns empty string');
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("falls back to H2 when no H3 headings exist at all", async () => {
|
|
694
|
+
// Backwards-compat: files with only H2 topic headers must still filter.
|
|
695
|
+
const h2OnlyKnowledge = `# Project Knowledge
|
|
696
|
+
|
|
697
|
+
## Database Patterns
|
|
698
|
+
Use prepared statements.
|
|
699
|
+
|
|
700
|
+
## API Design
|
|
701
|
+
REST with OpenAPI.
|
|
702
|
+
`;
|
|
703
|
+
|
|
704
|
+
const result = await queryKnowledge(h2OnlyKnowledge, ['database']);
|
|
705
|
+
|
|
706
|
+
assert.match(result, /Database Patterns/, 'H2-only file falls back to H2 filtering');
|
|
707
|
+
assert.ok(!result.includes('API Design'), 'non-matching H2 section excluded');
|
|
708
|
+
});
|
|
630
709
|
});
|
|
@@ -2,6 +2,7 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
5
6
|
|
|
6
7
|
test("copyPlanningArtifacts skips when source and destination .gsd resolve to the same path", () => {
|
|
7
8
|
const srcPath = join(import.meta.dirname, "..", "auto-worktree.ts");
|
|
@@ -10,7 +11,7 @@ test("copyPlanningArtifacts skips when source and destination .gsd resolve to th
|
|
|
10
11
|
const fnIdx = src.indexOf("function copyPlanningArtifacts");
|
|
11
12
|
assert.ok(fnIdx !== -1, "copyPlanningArtifacts function exists");
|
|
12
13
|
|
|
13
|
-
const fnBody = src
|
|
14
|
+
const fnBody = extractSourceRegion(src, "function copyPlanningArtifacts");
|
|
14
15
|
|
|
15
16
|
const guardIdx = fnBody.indexOf("if (isSamePath(srcGsd, dstGsd)) return;");
|
|
16
17
|
const copyIdx = fnBody.indexOf("safeCopyRecursive(join(srcGsd, \"milestones\")");
|
|
@@ -11,6 +11,7 @@ import { describe, it } from 'node:test'
|
|
|
11
11
|
import assert from 'node:assert/strict'
|
|
12
12
|
import { readFileSync } from 'node:fs'
|
|
13
13
|
import { resolve } from 'node:path'
|
|
14
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
14
15
|
|
|
15
16
|
const template = readFileSync(
|
|
16
17
|
resolve(process.cwd(), 'src', 'resources', 'extensions', 'gsd', 'prompts', 'guided-discuss-slice.md'),
|
|
@@ -37,7 +38,7 @@ describe('discuss-slice structuredQuestionsAvailable template variable', () => {
|
|
|
37
38
|
const falseIdx = template.indexOf('`{{structuredQuestionsAvailable}}` is `false`')
|
|
38
39
|
assert.ok(falseIdx !== -1)
|
|
39
40
|
|
|
40
|
-
const afterFalse = template
|
|
41
|
+
const afterFalse = extractSourceRegion(template, '`{{structuredQuestionsAvailable}}` is `false`')
|
|
41
42
|
assert.ok(
|
|
42
43
|
afterFalse.includes('plain text'),
|
|
43
44
|
'when structuredQuestionsAvailable is false, questions should be in plain text',
|
|
@@ -22,6 +22,7 @@ import { join, dirname } from "node:path";
|
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
|
|
24
24
|
import { DISCUSS_TOOLS_ALLOWLIST } from "../constants.ts";
|
|
25
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
25
26
|
|
|
26
27
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
const guidedFlowSource = readFileSync(join(__dirname, "..", "guided-flow.ts"), "utf-8");
|
|
@@ -58,7 +59,7 @@ describe("#3616 — discuss tool scoping must not leak across sessions", () => {
|
|
|
58
59
|
);
|
|
59
60
|
const newSessionStart = agentSessionSource.indexOf("async newSession(options?:");
|
|
60
61
|
assert.ok(newSessionStart >= 0, "should find newSession");
|
|
61
|
-
const body = agentSessionSource
|
|
62
|
+
const body = extractSourceRegion(agentSessionSource, "async newSession(options?:");
|
|
62
63
|
|
|
63
64
|
// Both branches (cwd-changed and cwd-unchanged) must include extension tools
|
|
64
65
|
assert.ok(
|
|
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { join, dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { AutoSession } from "../auto/session.ts";
|
|
7
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
7
8
|
|
|
8
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
10
|
|
|
@@ -20,7 +21,7 @@ describe("double mergeAndExit guard (#2645)", () => {
|
|
|
20
21
|
const completeIdx = phasesSrc.indexOf('state.phase === "complete"');
|
|
21
22
|
assert.ok(completeIdx > 0, "phases.ts should have a 'complete' phase check");
|
|
22
23
|
|
|
23
|
-
const afterComplete = phasesSrc
|
|
24
|
+
const afterComplete = extractSourceRegion(phasesSrc, 'state.phase === "complete"');
|
|
24
25
|
const mergeIdx = afterComplete.indexOf("deps.resolver.mergeAndExit");
|
|
25
26
|
const flagIdx = afterComplete.indexOf("s.milestoneMergedInPhases = true");
|
|
26
27
|
|
|
@@ -42,7 +43,7 @@ describe("double mergeAndExit guard (#2645)", () => {
|
|
|
42
43
|
const allCompleteIdx = phasesSrc.indexOf("incomplete.length === 0");
|
|
43
44
|
assert.ok(allCompleteIdx > 0, "phases.ts should have an all-milestones-complete check");
|
|
44
45
|
|
|
45
|
-
const afterAllComplete = phasesSrc
|
|
46
|
+
const afterAllComplete = extractSourceRegion(phasesSrc, "incomplete.length === 0");
|
|
46
47
|
const mergeIdx = afterAllComplete.indexOf("deps.resolver.mergeAndExit");
|
|
47
48
|
const flagIdx = afterAllComplete.indexOf("s.milestoneMergedInPhases = true");
|
|
48
49
|
|
|
@@ -64,7 +65,7 @@ describe("double mergeAndExit guard (#2645)", () => {
|
|
|
64
65
|
const step4Idx = autoSrc.indexOf("Step 4: Auto-worktree exit");
|
|
65
66
|
assert.ok(step4Idx > 0, "auto.ts should have Step 4 worktree exit");
|
|
66
67
|
|
|
67
|
-
const step4Block = autoSrc
|
|
68
|
+
const step4Block = extractSourceRegion(autoSrc, "Step 4: Auto-worktree exit");
|
|
68
69
|
assert.ok(
|
|
69
70
|
step4Block.includes("milestoneMergedInPhases"),
|
|
70
71
|
"stopAuto Step 4 must check milestoneMergedInPhases before merging",
|
|
@@ -13,6 +13,7 @@ import assert from "node:assert/strict";
|
|
|
13
13
|
import { readFileSync } from "node:fs";
|
|
14
14
|
import { join, dirname } from "node:path";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
16
17
|
|
|
17
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const RECOVERY_PATH = join(__dirname, "..", "bootstrap", "agent-end-recovery.ts");
|
|
@@ -30,7 +31,7 @@ test("agent-end-recovery.ts does not pause on aborted messages with empty conten
|
|
|
30
31
|
assert.ok(abortIdx > -1, "abort handler must exist in agent-end-recovery.ts");
|
|
31
32
|
|
|
32
33
|
// Extract the region around the abort handler (enough to see the guard logic)
|
|
33
|
-
const abortRegion = source
|
|
34
|
+
const abortRegion = extractSourceRegion(source, 'stopReason === "aborted"', { fromIdx: abortIdx });
|
|
34
35
|
|
|
35
36
|
// Must check for empty content before pausing
|
|
36
37
|
assert.ok(
|
|
@@ -48,7 +49,7 @@ test("agent-end-recovery.ts routes empty-content aborted messages to resolveAgen
|
|
|
48
49
|
assert.ok(abortIdx > -1, "abort handler must exist");
|
|
49
50
|
|
|
50
51
|
// Get the full abort handling block (from the if to the next stopReason check or success path)
|
|
51
|
-
const afterAbort = source
|
|
52
|
+
const afterAbort = extractSourceRegion(source, 'stopReason === "aborted"');
|
|
52
53
|
|
|
53
54
|
// The abort block must have a code path that calls resolveAgentEnd (for empty-content case)
|
|
54
55
|
assert.ok(
|
|
@@ -63,7 +64,7 @@ test("agent-end-recovery.ts checks for errorMessage presence in abort handler (#
|
|
|
63
64
|
const abortIdx = source.indexOf('stopReason === "aborted"');
|
|
64
65
|
assert.ok(abortIdx > -1, "abort handler must exist");
|
|
65
66
|
|
|
66
|
-
const abortRegion = source
|
|
67
|
+
const abortRegion = extractSourceRegion(source, 'stopReason === "aborted"');
|
|
67
68
|
|
|
68
69
|
// Fatal aborts should have error context (errorMessage field).
|
|
69
70
|
// The handler should check for this to distinguish fatal from non-fatal aborts.
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* isolated unit testing.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import {createTestContext, extractSourceRegion } from "./test-helpers.ts";
|
|
20
20
|
import {
|
|
21
21
|
withTimeout,
|
|
22
22
|
FINALIZE_PRE_TIMEOUT_MS,
|
|
@@ -163,7 +163,7 @@ function getRunFinalizeBody(phasesSource: string): string {
|
|
|
163
163
|
assertTrue(preVerIdx > 0, "postUnitPreVerification should appear in runFinalize");
|
|
164
164
|
|
|
165
165
|
// The first withTimeout should wrap postUnitPreVerification (not postUnitPostVerification)
|
|
166
|
-
const firstWithTimeout = fnBody
|
|
166
|
+
const firstWithTimeout = extractSourceRegion(fnBody, "withTimeout(");
|
|
167
167
|
assertTrue(
|
|
168
168
|
firstWithTimeout.includes("postUnitPreVerification"),
|
|
169
169
|
"first withTimeout in runFinalize should wrap postUnitPreVerification",
|
|
@@ -14,6 +14,7 @@ import assert from "node:assert/strict";
|
|
|
14
14
|
import { readFileSync } from "node:fs";
|
|
15
15
|
import { join, dirname } from "node:path";
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
17
18
|
|
|
18
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
const gsdDir = join(__dirname, "..");
|
|
@@ -43,7 +44,7 @@ describe("forensics hook compound key parsing (#2826)", () => {
|
|
|
43
44
|
it("detectMissingArtifacts delegates to splitCompletedKey", () => {
|
|
44
45
|
const fnStart = forensicsSrc.indexOf("function detectMissingArtifacts(");
|
|
45
46
|
assert.ok(fnStart !== -1, "detectMissingArtifacts must exist in forensics.ts");
|
|
46
|
-
const fnBody = forensicsSrc
|
|
47
|
+
const fnBody = extractSourceRegion(forensicsSrc, "function detectMissingArtifacts(");
|
|
47
48
|
assert.ok(
|
|
48
49
|
fnBody.includes("splitCompletedKey("),
|
|
49
50
|
"detectMissingArtifacts must call splitCompletedKey() rather than inline the split logic",
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the #4764 forensics integration — verifies that
|
|
3
|
+
* buildForensicReport picks up worktree telemetry aggregates and emits
|
|
4
|
+
* anomalies for orphans and auto-exits with unmerged work.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it } from "node:test";
|
|
8
|
+
import assert from "node:assert/strict";
|
|
9
|
+
import { readFileSync, mkdirSync, rmSync } from "node:fs";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
emitWorktreeOrphaned,
|
|
17
|
+
emitAutoExit,
|
|
18
|
+
emitWorktreeCreated,
|
|
19
|
+
emitWorktreeMerged,
|
|
20
|
+
} from "../worktree-telemetry.ts";
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const gsdDir = join(__dirname, "..");
|
|
24
|
+
|
|
25
|
+
function makeTmpBase(): string {
|
|
26
|
+
const base = join(tmpdir(), `gsd-for-tel-test-${randomUUID()}`);
|
|
27
|
+
mkdirSync(join(base, ".gsd"), { recursive: true });
|
|
28
|
+
return base;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function cleanup(base: string): void {
|
|
32
|
+
try { rmSync(base, { recursive: true, force: true }); } catch { /* */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe("#4764 forensics + worktree telemetry integration", () => {
|
|
36
|
+
const forensicsSrc = readFileSync(join(gsdDir, "forensics.ts"), "utf-8");
|
|
37
|
+
|
|
38
|
+
it("forensics.ts imports the telemetry summarizer", () => {
|
|
39
|
+
assert.ok(
|
|
40
|
+
forensicsSrc.includes("summarizeWorktreeTelemetry"),
|
|
41
|
+
"forensics must consume the telemetry aggregator",
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("forensics.ts does NOT call queryJournal directly (memory guard)", () => {
|
|
46
|
+
// The same invariant guarded by forensics-journal.test.ts — re-asserted
|
|
47
|
+
// here so this feature change doesn't regress it.
|
|
48
|
+
assert.ok(
|
|
49
|
+
!forensicsSrc.includes("queryJournal("),
|
|
50
|
+
"forensics.ts must route journal reads through aggregators, not queryJournal",
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("ForensicReport includes worktreeTelemetry field", () => {
|
|
55
|
+
assert.ok(
|
|
56
|
+
forensicsSrc.includes("worktreeTelemetry"),
|
|
57
|
+
"report shape must include the telemetry summary",
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("formatReportForPrompt gates Worktree Telemetry section on signal", () => {
|
|
62
|
+
assert.ok(
|
|
63
|
+
forensicsSrc.includes("Worktree Telemetry"),
|
|
64
|
+
"prompt formatter must include a Worktree Telemetry section",
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("new anomaly types worktree-orphan and worktree-unmerged-exit exist on the union", () => {
|
|
69
|
+
assert.ok(forensicsSrc.includes('"worktree-orphan"'));
|
|
70
|
+
assert.ok(forensicsSrc.includes('"worktree-unmerged-exit"'));
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("buildForensicReport surfaces worktree-orphan anomaly when the journal shows an in-progress orphan", async () => {
|
|
74
|
+
const base = makeTmpBase();
|
|
75
|
+
try {
|
|
76
|
+
// Seed the journal with one in-progress orphan event
|
|
77
|
+
emitWorktreeOrphaned(base, "M001", {
|
|
78
|
+
reason: "in-progress-unmerged",
|
|
79
|
+
commitsAhead: 3,
|
|
80
|
+
worktreeDirExists: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const { buildForensicReport } = await import("../forensics.ts");
|
|
84
|
+
const report = await buildForensicReport(base);
|
|
85
|
+
|
|
86
|
+
const orphanAnomalies = report.anomalies.filter(a => a.type === "worktree-orphan");
|
|
87
|
+
assert.ok(orphanAnomalies.length >= 1, `expected a worktree-orphan anomaly; got ${JSON.stringify(report.anomalies.map(a => a.type))}`);
|
|
88
|
+
assert.equal(orphanAnomalies[0].severity, "warning", "in-progress orphan should be a warning");
|
|
89
|
+
|
|
90
|
+
// Aggregate fields surface in the telemetry summary
|
|
91
|
+
assert.ok(report.worktreeTelemetry, "report should carry the telemetry summary");
|
|
92
|
+
assert.equal(report.worktreeTelemetry!.orphansDetected, 1);
|
|
93
|
+
assert.equal(report.worktreeTelemetry!.orphansByReason["in-progress-unmerged"], 1);
|
|
94
|
+
} finally { cleanup(base); }
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("buildForensicReport surfaces worktree-unmerged-exit anomaly when auto-exit left work unmerged", async () => {
|
|
98
|
+
const base = makeTmpBase();
|
|
99
|
+
try {
|
|
100
|
+
emitAutoExit(base, { reason: "pause", milestoneId: "M002", milestoneMerged: false });
|
|
101
|
+
emitAutoExit(base, { reason: "stop", milestoneId: "M002", milestoneMerged: false });
|
|
102
|
+
emitAutoExit(base, { reason: "all-complete", milestoneId: "M001", milestoneMerged: true });
|
|
103
|
+
|
|
104
|
+
const { buildForensicReport } = await import("../forensics.ts");
|
|
105
|
+
const report = await buildForensicReport(base);
|
|
106
|
+
|
|
107
|
+
const unmergedExitAnomalies = report.anomalies.filter(a => a.type === "worktree-unmerged-exit");
|
|
108
|
+
assert.equal(unmergedExitAnomalies.length, 1, "exactly one aggregate unmerged-exit anomaly");
|
|
109
|
+
assert.equal(unmergedExitAnomalies[0].severity, "warning");
|
|
110
|
+
assert.ok(
|
|
111
|
+
unmergedExitAnomalies[0].summary.includes("2"),
|
|
112
|
+
"summary should mention the count (2 unmerged exits out of 3 total)",
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
assert.equal(report.worktreeTelemetry!.exitsWithUnmergedWork, 2);
|
|
116
|
+
} finally { cleanup(base); }
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("buildForensicReport emits no telemetry anomalies when there are no signals", async () => {
|
|
120
|
+
const base = makeTmpBase();
|
|
121
|
+
try {
|
|
122
|
+
// Healthy path — worktree created and merged without incident
|
|
123
|
+
emitWorktreeCreated(base, "M001");
|
|
124
|
+
emitWorktreeMerged(base, "M001", {
|
|
125
|
+
reason: "milestone-complete",
|
|
126
|
+
durationMs: 250,
|
|
127
|
+
conflict: false,
|
|
128
|
+
});
|
|
129
|
+
emitAutoExit(base, { reason: "all-complete", milestoneId: "M001", milestoneMerged: true });
|
|
130
|
+
|
|
131
|
+
const { buildForensicReport } = await import("../forensics.ts");
|
|
132
|
+
const report = await buildForensicReport(base);
|
|
133
|
+
|
|
134
|
+
const telemetryAnomalies = report.anomalies.filter(a =>
|
|
135
|
+
a.type === "worktree-orphan" || a.type === "worktree-unmerged-exit"
|
|
136
|
+
);
|
|
137
|
+
assert.deepStrictEqual(telemetryAnomalies, [], "no orphans, no unmerged exits → no telemetry anomalies");
|
|
138
|
+
|
|
139
|
+
assert.equal(report.worktreeTelemetry!.worktreesCreated, 1);
|
|
140
|
+
assert.equal(report.worktreeTelemetry!.worktreesMerged, 1);
|
|
141
|
+
assert.equal(report.worktreeTelemetry!.orphansDetected, 0);
|
|
142
|
+
assert.equal(report.worktreeTelemetry!.exitsWithUnmergedWork, 0);
|
|
143
|
+
} finally { cleanup(base); }
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -18,6 +18,7 @@ import { readFileSync } from "node:fs";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { test, describe } from "node:test";
|
|
20
20
|
import assert from "node:assert/strict";
|
|
21
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
21
22
|
|
|
22
23
|
const TIMERS_SRC = readFileSync(
|
|
23
24
|
join(import.meta.dirname, "..", "auto-timers.ts"),
|
|
@@ -97,7 +98,11 @@ describe("#2527 Bug 2: null guard after async recovery prevents crash", () => {
|
|
|
97
98
|
|
|
98
99
|
// The null guard must appear between the recovery call and the next
|
|
99
100
|
// writeUnitRuntimeRecord that accesses s.currentUnit.startedAt
|
|
100
|
-
const afterRecovery =
|
|
101
|
+
const afterRecovery = extractSourceRegion(
|
|
102
|
+
TIMERS_SRC,
|
|
103
|
+
'recoverTimedOutUnit(ctx, pi, unitType, unitId, "idle"',
|
|
104
|
+
{ fromIdx: idleRecovery },
|
|
105
|
+
);
|
|
101
106
|
assert.ok(
|
|
102
107
|
afterRecovery.includes("if (!s.currentUnit) return"),
|
|
103
108
|
"null guard for s.currentUnit must exist after idle recoverTimedOutUnit",
|
|
@@ -10,6 +10,7 @@ import assert from 'node:assert/strict';
|
|
|
10
10
|
import { readFileSync } from 'node:fs';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
12
|
import { dirname, join } from 'node:path';
|
|
13
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = dirname(__filename);
|
|
@@ -30,7 +31,7 @@ describe('import done milestones as complete (#3699)', () => {
|
|
|
30
31
|
// Find the all-done guard and verify it sets 'complete'
|
|
31
32
|
const everyIdx = importerSrc.indexOf('roadmap.slices.every(s => s.done)');
|
|
32
33
|
assert.ok(everyIdx > -1, 'all-slices-done check should exist');
|
|
33
|
-
const afterCheck = importerSrc.
|
|
34
|
+
const afterCheck = extractSourceRegion(importerSrc, 'roadmap.slices.every(s => s.done)');
|
|
34
35
|
assert.match(afterCheck, /milestoneStatus\s*=\s*'complete'/,
|
|
35
36
|
'should set milestoneStatus to complete when all slices are done');
|
|
36
37
|
});
|
package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import test from "node:test";
|
|
14
14
|
import assert from "node:assert/strict";
|
|
15
|
+
import { extractSourceRegion } from "../test-helpers.ts";
|
|
15
16
|
import {
|
|
16
17
|
mkdtempSync,
|
|
17
18
|
mkdirSync,
|
|
@@ -115,7 +116,7 @@ test("auto-loop 'all milestones complete' path merges before stopping (#962)", (
|
|
|
115
116
|
helperIdx > -1,
|
|
116
117
|
"WorktreeResolver.mergeAndExit helper should exist",
|
|
117
118
|
);
|
|
118
|
-
const helperBlock = resolverSrc
|
|
119
|
+
const helperBlock = extractSourceRegion(resolverSrc, "mergeAndExit(milestoneId");
|
|
119
120
|
assert.ok(
|
|
120
121
|
helperBlock.includes('mode === "worktree"') ||
|
|
121
122
|
helperBlock.includes('mode: "worktree"'),
|
|
@@ -8,6 +8,7 @@ import assert from "node:assert/strict";
|
|
|
8
8
|
import { readFileSync } from "node:fs";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { extractSourceRegion } from "./test-helpers.ts";
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
|
|
@@ -62,7 +63,7 @@ describe("interactive routing bypass (#3962)", () => {
|
|
|
62
63
|
);
|
|
63
64
|
// The function should check isAutoMode before routing synthesis
|
|
64
65
|
const fnIdx = modelSelectionSrc.indexOf("function resolvePreferredModelConfig");
|
|
65
|
-
const fnBody = modelSelectionSrc
|
|
66
|
+
const fnBody = extractSourceRegion(modelSelectionSrc, "function resolvePreferredModelConfig");
|
|
66
67
|
assert.ok(
|
|
67
68
|
fnBody.includes("isAutoMode"),
|
|
68
69
|
"resolvePreferredModelConfig should accept isAutoMode parameter",
|
|
@@ -137,8 +138,13 @@ describe("model downgrade notifications always visible (#3962)", () => {
|
|
|
137
138
|
const escalatedIdx = modelSelectionSrc.indexOf("if (escalated)");
|
|
138
139
|
assert.ok(escalatedIdx > 0, "escalation block should exist");
|
|
139
140
|
|
|
140
|
-
// Get the block from "if (escalated)" to the next
|
|
141
|
-
|
|
141
|
+
// Get the block from "if (escalated)" to the next distinctive marker
|
|
142
|
+
// (the capability-override loading that immediately follows).
|
|
143
|
+
const block = extractSourceRegion(
|
|
144
|
+
modelSelectionSrc,
|
|
145
|
+
"if (escalated)",
|
|
146
|
+
"// Load user capability overrides",
|
|
147
|
+
);
|
|
142
148
|
assert.ok(
|
|
143
149
|
block.includes("Tier escalation:"),
|
|
144
150
|
"escalation block should contain the notification",
|