@opengsd/gsd-pi 1.1.1-dev.154fd443 → 1.1.1-dev.1854a79a
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/project-sessions.js +4 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/browser-tools/index.js +39 -22
- package/dist/resources/extensions/browser-tools/state.js +12 -0
- package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
- package/dist/resources/extensions/browser-tools/utils.js +3 -3
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
- package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
- package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
- package/dist/resources/extensions/gsd/auto-prompts.js +14 -1
- package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
- package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
- package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
- package/dist/resources/extensions/gsd/auto.js +62 -464
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +2 -1
- package/dist/resources/extensions/gsd/debug-logger.js +10 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
- package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
- package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +9 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
- package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +6 -6
- 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/api/boot/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +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 +6 -6
- package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
- package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
- package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- 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/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
- package/package.json +6 -4
- package/packages/cloud-mcp-gateway/package.json +2 -2
- package/packages/contracts/package.json +1 -1
- package/packages/daemon/package.json +4 -4
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
- package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
- package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
- package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/package.json +3 -3
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +0 -34
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +0 -34
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +7 -7
- package/packages/pi-tui/package.json +2 -2
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/browser-tools/index.ts +39 -22
- package/src/resources/extensions/browser-tools/state.ts +13 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
- package/src/resources/extensions/browser-tools/utils.ts +3 -3
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
- package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
- package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
- package/src/resources/extensions/gsd/auto-prompts.ts +21 -1
- package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
- package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
- package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
- package/src/resources/extensions/gsd/auto.ts +81 -500
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +2 -0
- package/src/resources/extensions/gsd/debug-logger.ts +11 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
- package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
- package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +9 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
- package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
- package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
- package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
- package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
- package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
- package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
- package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
- package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
- package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
- package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
- package/dist/web/standalone/.next/server/chunks/678.js +0 -2
- /package/dist/web/standalone/.next/static/{vAecbJ3K9eO213bAxU8Mi → h38jfi0dnRY0y3hbyBszg}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{vAecbJ3K9eO213bAxU8Mi → h38jfi0dnRY0y3hbyBszg}/_ssgManifest.js +0 -0
|
@@ -267,12 +267,18 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
267
267
|
// Non-fatal — dispatch continues without STATE.md if rebuild fails
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
+
// #442 Phase 1.7: resolve repo-ness once per gate. ensureWorkspaceGitReady
|
|
271
|
+
// has already run, and nothing below initializes/deinitializes the repo, so
|
|
272
|
+
// the two downstream blocks (integration-branch check, stale-changes
|
|
273
|
+
// snapshot) can share one nativeIsRepo result instead of querying twice.
|
|
274
|
+
const isRepo = nativeIsRepo(basePath);
|
|
275
|
+
|
|
270
276
|
// ── Integration branch existence check ──
|
|
271
277
|
// If the active milestone's recorded integration branch no longer exists in
|
|
272
278
|
// git, the merge-back at the end of the milestone will fail. Block dispatch
|
|
273
279
|
// now to surface this before work is lost.
|
|
274
280
|
try {
|
|
275
|
-
if (
|
|
281
|
+
if (isRepo) {
|
|
276
282
|
const state = await deriveState(basePath);
|
|
277
283
|
if (state.activeMilestone) {
|
|
278
284
|
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
@@ -296,7 +302,7 @@ export async function preDispatchHealthGate(basePath: string): Promise<PreDispat
|
|
|
296
302
|
// If the working tree is dirty and no commit has happened recently,
|
|
297
303
|
// create a safety snapshot so work isn't lost if the next unit crashes.
|
|
298
304
|
try {
|
|
299
|
-
if (
|
|
305
|
+
if (isRepo) {
|
|
300
306
|
const prefs = loadEffectiveGSDPreferences()?.preferences ?? {};
|
|
301
307
|
// `git.snapshots: false` is the canonical toggle that disables WIP
|
|
302
308
|
// snapshot commits — honour it before touching the threshold path (#4420).
|
|
@@ -9,11 +9,10 @@
|
|
|
9
9
|
// Critical invariant: rendered markdown must round-trip through
|
|
10
10
|
// parseRoadmap(), parsePlan(), parseSummary() in files.ts.
|
|
11
11
|
|
|
12
|
-
import { readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { readFileSync, existsSync, mkdirSync, statSync } from "node:fs";
|
|
13
13
|
import { logWarning } from "./workflow-logger.js";
|
|
14
14
|
import { isClosedStatus } from "./status-guards.js";
|
|
15
15
|
import { dirname, join, relative } from "node:path";
|
|
16
|
-
import { createRequire } from "node:module";
|
|
17
16
|
import {
|
|
18
17
|
getAllMilestones,
|
|
19
18
|
getMilestone,
|
|
@@ -38,7 +37,8 @@ import {
|
|
|
38
37
|
buildTaskFileName,
|
|
39
38
|
buildSliceFileName,
|
|
40
39
|
} from "./paths.js";
|
|
41
|
-
import { saveFile, clearParseCache } from "./files.js";
|
|
40
|
+
import { saveFile, clearParseCache, registerCacheClearCallback } from "./files.js";
|
|
41
|
+
import { parseRoadmap, parsePlan } from "./parsers-legacy.js";
|
|
42
42
|
import { invalidateStateCache } from "./state.js";
|
|
43
43
|
import { clearPathCache } from "./paths.js";
|
|
44
44
|
import type { RiskLevel } from "./types.js";
|
|
@@ -738,20 +738,41 @@ export interface StaleEntry {
|
|
|
738
738
|
* Returns a list of stale entries with file path and reason.
|
|
739
739
|
* Logs to stderr when stale files are detected.
|
|
740
740
|
*/
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
741
|
+
// #442 Phase 1.5: cache parsed ROADMAP/PLAN projections by file identity
|
|
742
|
+
// (path + mtimeMs + size) so an unchanged projection skips readFileSync AND
|
|
743
|
+
// the parse entirely on repeated dispatches. The DB-vs-disk comparison below
|
|
744
|
+
// still runs every call against fresh DB rows — only the disk-parse half is
|
|
745
|
+
// memoized, and parsed output depends solely on file bytes, so this is
|
|
746
|
+
// behavior-preserving. Invalidation rides the existing clearParseCache()
|
|
747
|
+
// callback chain (fired by invalidateCaches() after every projection write and
|
|
748
|
+
// by reconcileBeforeDispatch repairs), so a changed file always re-parses.
|
|
749
|
+
interface CachedProjection { mtimeMs: number; size: number; parsed: unknown }
|
|
750
|
+
const _projectionParseCache = new Map<string, CachedProjection>();
|
|
751
|
+
registerCacheClearCallback(() => _projectionParseCache.clear());
|
|
752
|
+
|
|
753
|
+
function parseProjectionByIdentity(path: string, parse: (content: string) => unknown): unknown {
|
|
754
|
+
let st: ReturnType<typeof statSync> | null = null;
|
|
755
|
+
try { st = statSync(path); } catch { st = null; }
|
|
756
|
+
if (st) {
|
|
757
|
+
const hit = _projectionParseCache.get(path);
|
|
758
|
+
if (hit && hit.mtimeMs === st.mtimeMs && hit.size === st.size) {
|
|
759
|
+
return hit.parsed;
|
|
760
|
+
}
|
|
761
|
+
const parsed = parse(readFileSync(path, "utf-8"));
|
|
762
|
+
_projectionParseCache.set(path, { mtimeMs: st.mtimeMs, size: st.size, parsed });
|
|
763
|
+
return parsed;
|
|
753
764
|
}
|
|
765
|
+
// stat failed (e.g. file vanished between existsSync and here) — fall back to
|
|
766
|
+
// the original plain read+parse so error handling is unchanged.
|
|
767
|
+
return parse(readFileSync(path, "utf-8"));
|
|
768
|
+
}
|
|
754
769
|
|
|
770
|
+
export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
771
|
+
// parseRoadmap/parsePlan are statically imported (#442 Phase 1.4): the
|
|
772
|
+
// per-call createRequire("./parsers-legacy") that used to live here ran on
|
|
773
|
+
// every dispatch. The static `./parsers-legacy.js` specifier resolves in
|
|
774
|
+
// both packaged (.js) and source (.ts via the strip-types loader) contexts —
|
|
775
|
+
// the same form a dozen other modules already use.
|
|
755
776
|
const stale: StaleEntry[] = [];
|
|
756
777
|
const milestones = getAllMilestones();
|
|
757
778
|
|
|
@@ -762,8 +783,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
762
783
|
const roadmapPath = resolveRoadmapProjectionPath(basePath, milestone.id);
|
|
763
784
|
if (existsSync(roadmapPath)) {
|
|
764
785
|
try {
|
|
765
|
-
const
|
|
766
|
-
const parsed = parseRoadmap(content);
|
|
786
|
+
const parsed = parseProjectionByIdentity(roadmapPath, parseRoadmap) as ReturnType<typeof parseRoadmap>;
|
|
767
787
|
|
|
768
788
|
for (const slice of slices) {
|
|
769
789
|
const isCompleteInDb = isClosedStatus(slice.status);
|
|
@@ -795,8 +815,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
|
|
|
795
815
|
const planPath = resolveSliceFile(basePath, milestone.id, slice.id, "PLAN");
|
|
796
816
|
if (planPath && existsSync(planPath)) {
|
|
797
817
|
try {
|
|
798
|
-
const
|
|
799
|
-
const parsed = parsePlan(content);
|
|
818
|
+
const parsed = parseProjectionByIdentity(planPath, parsePlan) as ReturnType<typeof parsePlan>;
|
|
800
819
|
|
|
801
820
|
for (const task of tasks) {
|
|
802
821
|
const isDoneInDb = isClosedStatus(task.status);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
|
|
4
5
|
import type { ClaudeCodeMcpConfig } from "./preferences-types.js";
|
|
@@ -115,6 +116,12 @@ export function discoverMcpServerNames(projectDir: string): string[] {
|
|
|
115
116
|
return discoverMcpServers(projectDir).map((server) => server.name);
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
export function discoverUserMcpServerNames(): string[] {
|
|
120
|
+
const userSettingsPath = resolve(homedir(), ".claude", "settings.json");
|
|
121
|
+
const userSettings = readJsonFile(userSettingsPath, true) as ClaudeSettingsFile | undefined;
|
|
122
|
+
return collectServerEntries(userSettings?.mcpServers).map((s) => s.name);
|
|
123
|
+
}
|
|
124
|
+
|
|
118
125
|
export function computeMcpDisallowedTools(
|
|
119
126
|
modelId: string,
|
|
120
127
|
mcpConfig: ClaudeCodeMcpConfig | undefined,
|
|
@@ -13,6 +13,7 @@ import { GSDError, GSD_GIT_ERROR } from "./errors.js";
|
|
|
13
13
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
14
14
|
import { getErrorMessage } from "./error-utils.js";
|
|
15
15
|
import { isInfrastructureError } from "./auto/infra-errors.js";
|
|
16
|
+
import { debugCount } from "./debug-logger.js";
|
|
16
17
|
|
|
17
18
|
// Issue #453: keep auto-mode bookkeeping on the stable git CLI path unless a
|
|
18
19
|
// caller explicitly opts into the native helper.
|
|
@@ -140,6 +141,8 @@ function loadNative(): typeof nativeModule {
|
|
|
140
141
|
|
|
141
142
|
/** Run a git command via execFileSync. Returns trimmed stdout. */
|
|
142
143
|
function gitExec(basePath: string, args: string[], allowFailure = false): string {
|
|
144
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
145
|
+
debugCount("gitInvocations");
|
|
143
146
|
try {
|
|
144
147
|
return execFileSync("git", args, {
|
|
145
148
|
cwd: basePath,
|
|
@@ -169,6 +172,8 @@ function execGitFileSyncWithRetry(
|
|
|
169
172
|
args: string[],
|
|
170
173
|
options: Partial<ExecFileSyncOptionsWithStringEncoding>,
|
|
171
174
|
): string {
|
|
175
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
176
|
+
debugCount("gitInvocations");
|
|
172
177
|
try {
|
|
173
178
|
return execFileSync("git", args, {
|
|
174
179
|
cwd: basePath,
|
|
@@ -180,6 +185,8 @@ function execGitFileSyncWithRetry(
|
|
|
180
185
|
} catch (err) {
|
|
181
186
|
if (!isRetryableGitError(err)) throw err;
|
|
182
187
|
sleepSync(GIT_RETRY_DELAY_MS);
|
|
188
|
+
// Retry is a second physical shell-out — count it too.
|
|
189
|
+
debugCount("gitInvocations");
|
|
183
190
|
return execFileSync("git", args, {
|
|
184
191
|
cwd: basePath,
|
|
185
192
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -192,6 +199,8 @@ function execGitFileSyncWithRetry(
|
|
|
192
199
|
|
|
193
200
|
/** Run a git command via execFileSync. Returns trimmed stdout. */
|
|
194
201
|
function gitFileExec(basePath: string, args: string[], allowFailure = false): string {
|
|
202
|
+
// Counts git CLI shell-outs only (native libgit2 paths bypass this helper).
|
|
203
|
+
debugCount("gitInvocations");
|
|
195
204
|
try {
|
|
196
205
|
return execFileSync("git", args, {
|
|
197
206
|
cwd: basePath,
|
|
@@ -38,7 +38,7 @@ If slice research is inlined, trust its architectural findings, but verify every
|
|
|
38
38
|
|
|
39
39
|
1. If requirements are preloaded, identify owned and supporting Active requirements.
|
|
40
40
|
2. Call `memory_query` with keywords from the slice title and source files.
|
|
41
|
-
3.
|
|
41
|
+
3. Use the inlined Output Template sections already present in this prompt. Do not read template files from disk.
|
|
42
42
|
4. {{skillActivation}} Record expected executor skills in each task plan's `skills_used` frontmatter.
|
|
43
43
|
5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
|
|
44
44
|
6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
|
|
@@ -67,7 +67,12 @@ export interface ParsedRoadmap {
|
|
|
67
67
|
const TEMPLATE_TOKEN_RE = /\{\{[^}]+\}\}/;
|
|
68
68
|
const H2_RE = /^##\s+(.+)$/gm;
|
|
69
69
|
const H3_RE = /^###\s+(.+)$/gm;
|
|
70
|
-
|
|
70
|
+
// A milestone line is single-line by construction. Every inter-token gap uses
|
|
71
|
+
// horizontal-whitespace classes (`[^\S\n]`) rather than `\s`, because `\s`
|
|
72
|
+
// matches newlines: a line missing a valid separator would otherwise let the
|
|
73
|
+
// `\s+(?:—|--|-)\s+` clause "bridge" onto the NEXT bullet's `- `, consuming it
|
|
74
|
+
// as the separator and silently swallowing the following well-formed milestone.
|
|
75
|
+
const MILESTONE_LINE_RE = /^-[^\S\n]+\[([ x])\][^\S\n]+(M\d{3}):[^\S\n]+(.+?)[^\S\n]+(?:—|--|-)[^\S\n]+(.+)$/gm;
|
|
71
76
|
const SLICE_HEADER_RE = /^###\s+(S\d{2})\s*(?:—|--|-)\s+(.+)$/m;
|
|
72
77
|
const REQUIREMENT_HEADER_RE = /^###\s+(R\d{3})\s*(?:—|--|-)\s+(.+)$/m;
|
|
73
78
|
|
|
@@ -335,21 +335,42 @@ function detectDiskSliceIdDivergenceForMilestone(
|
|
|
335
335
|
return drifts;
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
type ArtifactDbDrift =
|
|
339
|
+
| DiskSliceIdDivergenceDrift
|
|
340
|
+
| ArtifactDbStatusDivergenceDrift
|
|
341
|
+
| CompletedMilestoneReopenedDrift;
|
|
342
|
+
|
|
343
|
+
// #442 Phase 1.6: the three artifact/DB drift handlers (disk-slice-id,
|
|
344
|
+
// artifact-db-status, completed-milestone-reopened) each call
|
|
345
|
+
// detectArtifactDbDrift and then filter for their own kind — so the full
|
|
346
|
+
// milestone→slice→task walk + artifact SQL + disk scan would run THREE times
|
|
347
|
+
// per detection pass and discard 2/3 of the work. Memoize the result per
|
|
348
|
+
// DriftContext so the three handlers share one computation. The key is the
|
|
349
|
+
// ctx object, which detectAllDrift rebuilds for every pass (and which is
|
|
350
|
+
// unreferenced once the pass ends, so the WeakMap entry is GC'd) — DB/disk
|
|
351
|
+
// state is immutable within a single pass (repairs run only after detection),
|
|
352
|
+
// so this is behavior-preserving. A fresh ctx (e.g. the maintenance command's
|
|
353
|
+
// inline { basePath, state }) always recomputes.
|
|
354
|
+
const _artifactDbDriftMemo = new WeakMap<DriftContext, ArtifactDbDrift[]>();
|
|
355
|
+
|
|
338
356
|
export function detectArtifactDbDrift(
|
|
357
|
+
state: GSDState,
|
|
358
|
+
ctx: DriftContext,
|
|
359
|
+
): ArtifactDbDrift[] {
|
|
360
|
+
const cached = _artifactDbDriftMemo.get(ctx);
|
|
361
|
+
if (cached) return cached;
|
|
362
|
+
const computed = computeArtifactDbDrift(state, ctx);
|
|
363
|
+
_artifactDbDriftMemo.set(ctx, computed);
|
|
364
|
+
return computed;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function computeArtifactDbDrift(
|
|
339
368
|
_state: GSDState,
|
|
340
369
|
ctx: DriftContext,
|
|
341
|
-
):
|
|
342
|
-
| DiskSliceIdDivergenceDrift
|
|
343
|
-
| ArtifactDbStatusDivergenceDrift
|
|
344
|
-
| CompletedMilestoneReopenedDrift
|
|
345
|
-
> {
|
|
370
|
+
): ArtifactDbDrift[] {
|
|
346
371
|
if (!isDbAvailable()) return [];
|
|
347
372
|
|
|
348
|
-
const drifts:
|
|
349
|
-
| DiskSliceIdDivergenceDrift
|
|
350
|
-
| ArtifactDbStatusDivergenceDrift
|
|
351
|
-
| CompletedMilestoneReopenedDrift
|
|
352
|
-
> = [];
|
|
373
|
+
const drifts: ArtifactDbDrift[] = [];
|
|
353
374
|
|
|
354
375
|
for (const milestone of getAllMilestones()) {
|
|
355
376
|
if (isClosedStatus(milestone.status)) continue;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: #442 Phase 1.6 — detectArtifactDbDrift is memoized per
|
|
3
|
+
// DriftContext so the three artifact/DB drift handlers share one
|
|
4
|
+
// milestone→slice→task walk per detection pass instead of recomputing it
|
|
5
|
+
// three times. Asserts external behavior: same ctx returns the same result
|
|
6
|
+
// (shared within a pass); a fresh ctx recomputes identical content (no
|
|
7
|
+
// cross-pass leakage).
|
|
8
|
+
|
|
9
|
+
import test from "node:test";
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
|
|
15
|
+
import { openDatabase, closeDatabase, insertMilestone, insertSlice } from "../gsd-db.ts";
|
|
16
|
+
import { detectArtifactDbDrift } from "../state-reconciliation/drift/artifact-db.ts";
|
|
17
|
+
import type { DriftContext } from "../state-reconciliation/types.ts";
|
|
18
|
+
import type { GSDState } from "../types.ts";
|
|
19
|
+
|
|
20
|
+
function stubState(): GSDState {
|
|
21
|
+
return {
|
|
22
|
+
activeMilestone: { id: "M001", title: "M" },
|
|
23
|
+
activeSlice: null,
|
|
24
|
+
activeTask: null,
|
|
25
|
+
phase: "executing",
|
|
26
|
+
recentDecisions: [],
|
|
27
|
+
blockers: [],
|
|
28
|
+
nextAction: "",
|
|
29
|
+
registry: [],
|
|
30
|
+
requirements: { active: 0, validated: 0, deferred: 0, outOfScope: 0, blocked: 0, total: 0 },
|
|
31
|
+
progress: { milestones: { done: 0, total: 1 } },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test("#442: detectArtifactDbDrift is memoized per DriftContext", (t) => {
|
|
36
|
+
const base = mkdtempSync(join(tmpdir(), "gsd-artifact-memo-"));
|
|
37
|
+
const sliceDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
|
|
38
|
+
mkdirSync(sliceDir, { recursive: true });
|
|
39
|
+
t.after(() => {
|
|
40
|
+
try { closeDatabase(); } catch { /* noop */ }
|
|
41
|
+
rmSync(base, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
openDatabase(join(base, ".gsd", "gsd.db"));
|
|
45
|
+
insertMilestone({ id: "M001", title: "M", status: "active" });
|
|
46
|
+
insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending", risk: "low", depends: [], sequence: 1 });
|
|
47
|
+
// A SUMMARY on disk while the slice is still pending = artifact/DB divergence.
|
|
48
|
+
writeFileSync(join(sliceDir, "S01-SUMMARY.md"), "# S01 Summary\n");
|
|
49
|
+
|
|
50
|
+
const state = stubState();
|
|
51
|
+
|
|
52
|
+
const ctx1: DriftContext = { basePath: base, state };
|
|
53
|
+
const first = detectArtifactDbDrift(state, ctx1);
|
|
54
|
+
const firstAgain = detectArtifactDbDrift(state, ctx1);
|
|
55
|
+
|
|
56
|
+
// Same ctx → cache hit → identical array reference (the 3 handlers in one
|
|
57
|
+
// pass share this exact result).
|
|
58
|
+
assert.strictEqual(firstAgain, first, "same ctx must return the memoized result");
|
|
59
|
+
assert.ok(first.length > 0, "fixture should produce at least one drift record");
|
|
60
|
+
|
|
61
|
+
// Fresh ctx → recomputed (distinct instance) but identical content.
|
|
62
|
+
const ctx2: DriftContext = { basePath: base, state };
|
|
63
|
+
const second = detectArtifactDbDrift(state, ctx2);
|
|
64
|
+
assert.notStrictEqual(second, first, "a fresh ctx must recompute, not reuse the prior pass");
|
|
65
|
+
assert.deepEqual(second, first, "recomputed result must be identical content");
|
|
66
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Project/App: gsd-pi
|
|
2
|
+
// File Purpose: Cover the getDebugCounters() snapshot getter added for the
|
|
3
|
+
// per-dispatch benchmark harness (issue #442, Phase 0.3). The harness itself
|
|
4
|
+
// (scripts/auto-dispatch-baseline.mjs) reads counters via this getter without
|
|
5
|
+
// disabling debug (which is what writeDebugSummary does).
|
|
6
|
+
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
import assert from 'node:assert';
|
|
9
|
+
import { mkdtempSync, mkdirSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { tmpdir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
enableDebug,
|
|
15
|
+
disableDebug,
|
|
16
|
+
debugCount,
|
|
17
|
+
getDebugCounters,
|
|
18
|
+
} from '../debug-logger.ts';
|
|
19
|
+
|
|
20
|
+
function tmpGsd(): string {
|
|
21
|
+
const tmp = mkdtempSync(join(tmpdir(), 'gsd-baseline-harness-'));
|
|
22
|
+
mkdirSync(join(tmp, '.gsd'), { recursive: true });
|
|
23
|
+
return tmp;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('getDebugCounters returns a live snapshot of the hot-path counters', () => {
|
|
27
|
+
enableDebug(tmpGsd());
|
|
28
|
+
|
|
29
|
+
debugCount('deriveStateCalls', 3);
|
|
30
|
+
debugCount('parseRoadmapCalls');
|
|
31
|
+
debugCount('parsePlanCalls', 2);
|
|
32
|
+
debugCount('gitInvocations', 7);
|
|
33
|
+
|
|
34
|
+
const snap = getDebugCounters();
|
|
35
|
+
assert.strictEqual(snap.deriveStateCalls, 3);
|
|
36
|
+
assert.strictEqual(snap.parseRoadmapCalls, 1);
|
|
37
|
+
assert.strictEqual(snap.parsePlanCalls, 2);
|
|
38
|
+
assert.strictEqual(snap.gitInvocations, 7);
|
|
39
|
+
|
|
40
|
+
disableDebug();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('getDebugCounters returns a copy — callers cannot mutate internal state', () => {
|
|
44
|
+
enableDebug(tmpGsd());
|
|
45
|
+
debugCount('gitInvocations', 5);
|
|
46
|
+
|
|
47
|
+
const snap = getDebugCounters() as Record<string, number>;
|
|
48
|
+
snap.gitInvocations = 999;
|
|
49
|
+
|
|
50
|
+
assert.strictEqual(getDebugCounters().gitInvocations, 5, 'internal counter must be unaffected by mutating the snapshot');
|
|
51
|
+
|
|
52
|
+
disableDebug();
|
|
53
|
+
});
|