@opengsd/gsd-pi 1.3.0-dev.72e3af2a → 1.3.0-dev.965d1788
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/README.md +1 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/verification-retry-policy.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +1 -1
- package/dist/resources/extensions/gsd/auto-verification.js +5 -2
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +6 -0
- package/dist/resources/extensions/gsd/closeout-consistency-gate.js +3 -1
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +3 -0
- package/dist/resources/extensions/gsd/flat-phase-migration.js +45 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -0
- package/dist/resources/extensions/gsd/gitignore.js +3 -0
- package/dist/resources/extensions/gsd/json-persistence.js +20 -0
- package/dist/resources/extensions/gsd/knowledge-backfill.js +18 -11
- package/dist/resources/extensions/gsd/memory-backfill.js +28 -21
- package/dist/resources/extensions/gsd/metrics.js +4 -4
- package/dist/resources/extensions/gsd/orphan-stash-audit.js +92 -16
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/safety/file-change-validator.js +5 -2
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -2
- package/dist/resources/extensions/gsd/undo.js +43 -6
- package/dist/resources/extensions/gsd/verification-gate.js +7 -0
- package/dist/resources/extensions/gsd/workflow-manifest.js +4 -1
- 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 +10 -10
- 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 +10 -10
- 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/package.json +1 -1
- 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/package.json +7 -7
- package/packages/mcp-server/package.json +4 -4
- package/packages/native/package.json +1 -1
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-ai/package.json +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/gsd/auto/session.ts +1 -0
- package/src/resources/extensions/gsd/auto/verification-retry-policy.ts +3 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +1 -1
- package/src/resources/extensions/gsd/auto-verification.ts +5 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +9 -0
- package/src/resources/extensions/gsd/closeout-consistency-gate.ts +2 -1
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +3 -0
- package/src/resources/extensions/gsd/flat-phase-migration.ts +44 -0
- package/src/resources/extensions/gsd/git-service.ts +1 -0
- package/src/resources/extensions/gsd/gitignore.ts +3 -0
- package/src/resources/extensions/gsd/json-persistence.ts +20 -0
- package/src/resources/extensions/gsd/knowledge-backfill.ts +16 -10
- package/src/resources/extensions/gsd/memory-backfill.ts +26 -20
- package/src/resources/extensions/gsd/metrics.ts +4 -4
- package/src/resources/extensions/gsd/orphan-stash-audit.ts +108 -20
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/safety/file-change-validator.ts +8 -2
- package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +28 -0
- package/src/resources/extensions/gsd/tests/complete-slice-reopen-handoff.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/decisions-projection-from-memories.test.ts +51 -0
- package/src/resources/extensions/gsd/tests/file-change-validator.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/flat-phase-migration.test.ts +37 -2
- package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +4 -0
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +11 -2
- package/src/resources/extensions/gsd/tests/json-persistence-atomic.test.ts +47 -0
- package/src/resources/extensions/gsd/tests/knowledge-backfill-projection.test.ts +35 -0
- package/src/resources/extensions/gsd/tests/merge-closeout-consistency-gate.test.ts +31 -1
- package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/metrics.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +91 -7
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +10 -0
- package/src/resources/extensions/gsd/tests/undo.test.ts +74 -0
- package/src/resources/extensions/gsd/tests/verification-retry-policy.test.ts +69 -4
- package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +54 -0
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -2
- package/src/resources/extensions/gsd/undo.ts +47 -6
- package/src/resources/extensions/gsd/verification-gate.ts +8 -0
- package/src/resources/extensions/gsd/workflow-manifest.ts +4 -1
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → -ji77QJck2IKIwrGza1Bs}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{O7xDYXO0r4zFhIzY1hrWV → -ji77QJck2IKIwrGza1Bs}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
verificationRetryKey,
|
|
12
12
|
VERIFICATION_RETRY_MAX_DELAY_MS,
|
|
13
13
|
} from "../auto/verification-retry-policy.ts";
|
|
14
|
+
import { formatFailureContext, formatFailureSignature } from "../verification-gate.ts";
|
|
15
|
+
import type { VerificationResult } from "../types.ts";
|
|
14
16
|
|
|
15
17
|
test("verificationRetryDelayMs uses exponential backoff with cap", () => {
|
|
16
18
|
assert.deepEqual(verificationRetryDelayMs(1, () => 0.5), {
|
|
@@ -31,9 +33,22 @@ test("verificationRetryDelayMs uses exponential backoff with cap", () => {
|
|
|
31
33
|
});
|
|
32
34
|
});
|
|
33
35
|
|
|
34
|
-
test("decideVerificationRetry pauses on
|
|
36
|
+
test("decideVerificationRetry pauses on repeated failure context across attempts", () => {
|
|
35
37
|
const failureContext = "lint failed\n";
|
|
36
|
-
const
|
|
38
|
+
const firstDecision = decideVerificationRetry({
|
|
39
|
+
unitType: "execute-task",
|
|
40
|
+
retryInfo: {
|
|
41
|
+
unitId: "M001/S01/T01",
|
|
42
|
+
failureContext,
|
|
43
|
+
attempt: 1,
|
|
44
|
+
},
|
|
45
|
+
previousFailureHash: undefined,
|
|
46
|
+
random: () => 0.5,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
assert.equal(firstDecision.action, "delay");
|
|
50
|
+
assert.equal(firstDecision.failureHash, hashVerificationFailureContext(failureContext));
|
|
51
|
+
|
|
37
52
|
const decision = decideVerificationRetry({
|
|
38
53
|
unitType: "execute-task",
|
|
39
54
|
retryInfo: {
|
|
@@ -41,7 +56,7 @@ test("decideVerificationRetry pauses on duplicate failure context", () => {
|
|
|
41
56
|
failureContext,
|
|
42
57
|
attempt: 2,
|
|
43
58
|
},
|
|
44
|
-
previousFailureHash: failureHash,
|
|
59
|
+
previousFailureHash: firstDecision.failureHash,
|
|
45
60
|
random: () => 0.5,
|
|
46
61
|
});
|
|
47
62
|
|
|
@@ -49,7 +64,7 @@ test("decideVerificationRetry pauses on duplicate failure context", () => {
|
|
|
49
64
|
action: "pause",
|
|
50
65
|
reason: "duplicate-failure-context",
|
|
51
66
|
key: verificationRetryKey("execute-task", "M001/S01/T01"),
|
|
52
|
-
failureHash,
|
|
67
|
+
failureHash: firstDecision.failureHash,
|
|
53
68
|
});
|
|
54
69
|
});
|
|
55
70
|
|
|
@@ -71,6 +86,56 @@ test("decideVerificationRetry delays changed failure context", () => {
|
|
|
71
86
|
assert.equal(decision.baseDelayMs, 4_000);
|
|
72
87
|
});
|
|
73
88
|
|
|
89
|
+
test("decideVerificationRetry pauses duplicate host-check failures with volatile stderr", () => {
|
|
90
|
+
const makeResult = (stderr: string): VerificationResult => ({
|
|
91
|
+
passed: false,
|
|
92
|
+
discoverySource: "package-json",
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
checks: [
|
|
95
|
+
{
|
|
96
|
+
command: "npm test",
|
|
97
|
+
exitCode: 1,
|
|
98
|
+
stdout: "",
|
|
99
|
+
stderr,
|
|
100
|
+
durationMs: 100,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
const first = makeResult("failed after 120ms in /tmp/gsd-a worker pid 101");
|
|
105
|
+
const second = makeResult("failed after 133ms in /tmp/gsd-b worker pid 202");
|
|
106
|
+
const firstContext = formatFailureContext(first);
|
|
107
|
+
const secondContext = formatFailureContext(second);
|
|
108
|
+
|
|
109
|
+
assert.notEqual(
|
|
110
|
+
hashVerificationFailureContext(firstContext),
|
|
111
|
+
hashVerificationFailureContext(secondContext),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const signature = formatFailureSignature(first);
|
|
115
|
+
assert.equal(signature, "npm test#1");
|
|
116
|
+
assert.equal(formatFailureSignature(second), signature);
|
|
117
|
+
|
|
118
|
+
const failureHash = hashVerificationFailureContext(signature);
|
|
119
|
+
const decision = decideVerificationRetry({
|
|
120
|
+
unitType: "execute-task",
|
|
121
|
+
retryInfo: {
|
|
122
|
+
unitId: "M001/S01/T01",
|
|
123
|
+
failureContext: secondContext,
|
|
124
|
+
signature: formatFailureSignature(second),
|
|
125
|
+
attempt: 2,
|
|
126
|
+
},
|
|
127
|
+
previousFailureHash: failureHash,
|
|
128
|
+
random: () => 0.5,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
assert.deepEqual(decision, {
|
|
132
|
+
action: "pause",
|
|
133
|
+
reason: "duplicate-failure-context",
|
|
134
|
+
key: verificationRetryKey("execute-task", "M001/S01/T01"),
|
|
135
|
+
failureHash,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
74
139
|
test("decideVerificationRetry pauses when retry context is missing", () => {
|
|
75
140
|
assert.deepEqual(
|
|
76
141
|
decideVerificationRetry({
|
|
@@ -380,6 +380,60 @@ test('workflow-manifest: bootstrapFromManifest preserves target_repositories', (
|
|
|
380
380
|
}
|
|
381
381
|
});
|
|
382
382
|
|
|
383
|
+
test('workflow-manifest: bootstrapFromManifest does not truncate projections when artifact content is empty', () => {
|
|
384
|
+
const base = tempDir();
|
|
385
|
+
openDatabase(tempDbPath(base));
|
|
386
|
+
try {
|
|
387
|
+
insertMilestone({ id: 'M001', title: 'Projection Milestone' });
|
|
388
|
+
insertArtifact({
|
|
389
|
+
path: '.gsd/milestones/M001/M001-PLAN.md',
|
|
390
|
+
artifact_type: 'PLAN',
|
|
391
|
+
milestone_id: 'M001',
|
|
392
|
+
slice_id: null,
|
|
393
|
+
task_id: null,
|
|
394
|
+
full_content: '# Manifest Plan',
|
|
395
|
+
});
|
|
396
|
+
insertArtifact({
|
|
397
|
+
path: '.gsd/milestones/M001/M001-VALIDATION.md',
|
|
398
|
+
artifact_type: 'VALIDATION',
|
|
399
|
+
milestone_id: 'M001',
|
|
400
|
+
slice_id: null,
|
|
401
|
+
task_id: null,
|
|
402
|
+
full_content: '# Manifest Validation',
|
|
403
|
+
});
|
|
404
|
+
writeManifest(base);
|
|
405
|
+
|
|
406
|
+
const planPath = path.join(base, '.gsd', 'milestones', 'M001', 'M001-PLAN.md');
|
|
407
|
+
const validationPath = path.join(base, '.gsd', 'milestones', 'M001', 'M001-VALIDATION.md');
|
|
408
|
+
fs.mkdirSync(path.dirname(planPath), { recursive: true });
|
|
409
|
+
fs.writeFileSync(planPath, '# Existing Plan\n\nKeep this.');
|
|
410
|
+
fs.writeFileSync(validationPath, '# Existing Validation\n\nKeep this.');
|
|
411
|
+
|
|
412
|
+
const manifestPath = path.join(base, '.gsd', 'state-manifest.json');
|
|
413
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as Record<string, unknown>;
|
|
414
|
+
const artifacts = manifest['artifacts'] as Array<Record<string, unknown>>;
|
|
415
|
+
const emptyArtifact = artifacts.find((r) => r['path'] === '.gsd/milestones/M001/M001-PLAN.md');
|
|
416
|
+
const absentArtifact = artifacts.find((r) => r['path'] === '.gsd/milestones/M001/M001-VALIDATION.md');
|
|
417
|
+
assert.ok(emptyArtifact !== undefined);
|
|
418
|
+
assert.ok(absentArtifact !== undefined);
|
|
419
|
+
emptyArtifact['full_content'] = '';
|
|
420
|
+
delete absentArtifact['full_content'];
|
|
421
|
+
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
422
|
+
closeDatabase();
|
|
423
|
+
|
|
424
|
+
const newDbPath = path.join(base, 'new.db');
|
|
425
|
+
openDatabase(newDbPath);
|
|
426
|
+
const restored = bootstrapFromManifest(base);
|
|
427
|
+
assert.strictEqual(restored, true);
|
|
428
|
+
|
|
429
|
+
assert.strictEqual(fs.readFileSync(planPath, 'utf-8'), '# Existing Plan\n\nKeep this.');
|
|
430
|
+
assert.strictEqual(fs.readFileSync(validationPath, 'utf-8'), '# Existing Validation\n\nKeep this.');
|
|
431
|
+
} finally {
|
|
432
|
+
closeDatabase();
|
|
433
|
+
cleanupDir(base);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
383
437
|
test('workflow-manifest: bootstrapFromManifest preserves optional rows from old manifests', () => {
|
|
384
438
|
const base = tempDir();
|
|
385
439
|
openDatabase(tempDbPath(base));
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
} from "../gsd-db.js";
|
|
24
24
|
import { gsdProjectionRoot, clearPathCache, relMilestoneFile } from "../paths.js";
|
|
25
25
|
import { resolveCanonicalMilestoneRoot } from "../worktree-manager.js";
|
|
26
|
-
import { isClosedStatus } from "../status-guards.js";
|
|
26
|
+
import { isClosedStatus, isDeferredStatus } from "../status-guards.js";
|
|
27
27
|
import { saveFile, clearParseCache } from "../files.js";
|
|
28
28
|
import { invalidateStateCache } from "../state.js";
|
|
29
29
|
import { stripIdPrefix } from "../workflow-projections.js";
|
|
@@ -180,7 +180,7 @@ export async function handleCompleteMilestone(
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
|
|
183
|
+
const incompleteSlices = slices.filter(s => !isClosedStatus(s.status) && !isDeferredStatus(s.status));
|
|
184
184
|
if (incompleteSlices.length > 0) {
|
|
185
185
|
const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
|
|
186
186
|
guardError = `incomplete slices: ${incompleteIds}`;
|
|
@@ -189,6 +189,7 @@ export async function handleCompleteMilestone(
|
|
|
189
189
|
|
|
190
190
|
// Deep check: verify all tasks in all slices are complete
|
|
191
191
|
for (const slice of slices) {
|
|
192
|
+
if (isDeferredStatus(slice.status)) continue;
|
|
192
193
|
const tasks = getSliceTasks(params.milestoneId, slice.id);
|
|
193
194
|
const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
|
|
194
195
|
if (incompleteTasks.length > 0) {
|
|
@@ -15,6 +15,7 @@ import { gsdRoot, resolveTasksDir, resolveSlicePath, resolveSliceFile, resolveTa
|
|
|
15
15
|
import { sendDesktopNotification } from "./notifications.js";
|
|
16
16
|
import { getTask, getSlice, getSliceTasks, updateTaskStatus, resetSliceCascade } from "./gsd-db.js";
|
|
17
17
|
import { renderPlanCheckboxes, renderRoadmapCheckboxes } from "./markdown-renderer.js";
|
|
18
|
+
import { UNIT_REGISTRY } from "./unit-registry.js";
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Undo the last completed unit: revert git commits,
|
|
@@ -42,16 +43,20 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
|
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
// Extract unit type and ID from the most recent activity log filename
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Extract unit type and ID from the most recent activity log filename.
|
|
47
|
+
// Both the unit type and the unit ID may contain hyphens, so anchor on the
|
|
48
|
+
// known unit-type vocabulary instead of guessing the unit-ID shape: a regex
|
|
49
|
+
// tuned to milestone-shaped IDs rejects project-level units whose IDs are
|
|
50
|
+
// symbolic (e.g. discuss-project uses PROJECT, workflow-preferences uses
|
|
51
|
+
// WORKFLOW-PREFS).
|
|
52
|
+
const parsed = parseActivityLogFilename(files[0]);
|
|
53
|
+
if (!parsed) {
|
|
49
54
|
ctx.ui.notify("Nothing to undo — could not parse latest activity log.", "warning");
|
|
50
55
|
return;
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
const unitType =
|
|
54
|
-
const unitId =
|
|
58
|
+
const unitType = parsed.unitType;
|
|
59
|
+
const unitId = parsed.unitId.replace(/-/g, "/");
|
|
55
60
|
|
|
56
61
|
if (!force) {
|
|
57
62
|
ctx.ui.notify(
|
|
@@ -100,6 +105,11 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
|
|
|
100
105
|
if (unitType === "execute-task" && task !== undefined && slice !== undefined) {
|
|
101
106
|
const [mid, sid, tid] = [milestone, slice, task];
|
|
102
107
|
planUpdated = uncheckTaskInPlan(basePath, mid, sid, tid);
|
|
108
|
+
if (getTask(mid, sid, tid)) {
|
|
109
|
+
updateTaskStatus(mid, sid, tid, "pending");
|
|
110
|
+
await renderPlanCheckboxes(basePath, mid, sid);
|
|
111
|
+
planUpdated = true;
|
|
112
|
+
}
|
|
103
113
|
}
|
|
104
114
|
|
|
105
115
|
// 3. Try to revert git commits from activity log
|
|
@@ -379,6 +389,37 @@ export async function handleResetSlice(
|
|
|
379
389
|
|
|
380
390
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
381
391
|
|
|
392
|
+
// Known unit types sorted longest-first so a more specific type (e.g.
|
|
393
|
+
// "execute-task-simple") matches before a prefix of it ("execute-task") when
|
|
394
|
+
// splitting "<seq>-<unitType>-<unitId>.jsonl".
|
|
395
|
+
const UNIT_TYPES_BY_LENGTH_DESC: readonly string[] = Object.keys(UNIT_REGISTRY).sort(
|
|
396
|
+
(a, b) => b.length - a.length,
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Parse an activity-log filename of the form `<seq>-<unitType>-<unitId>.jsonl`
|
|
401
|
+
* (the format written by activity-log.ts). Both the unit type and the unit ID
|
|
402
|
+
* may contain hyphens, so we anchor on the known unit-type vocabulary rather
|
|
403
|
+
* than guessing the ID shape. This keeps non-milestone IDs (e.g. PROJECT,
|
|
404
|
+
* WORKFLOW-PREFS) parseable. Returns null when the name has no sequence prefix
|
|
405
|
+
* or does not start with a recognised unit type.
|
|
406
|
+
*/
|
|
407
|
+
export function parseActivityLogFilename(
|
|
408
|
+
filename: string,
|
|
409
|
+
): { unitType: string; unitId: string } | null {
|
|
410
|
+
const seqMatch = filename.match(/^\d+-(.+)\.jsonl$/);
|
|
411
|
+
if (!seqMatch) return null;
|
|
412
|
+
const rest = seqMatch[1];
|
|
413
|
+
for (const unitType of UNIT_TYPES_BY_LENGTH_DESC) {
|
|
414
|
+
const prefix = `${unitType}-`;
|
|
415
|
+
if (rest.startsWith(prefix)) {
|
|
416
|
+
const unitId = rest.slice(prefix.length);
|
|
417
|
+
if (unitId.length > 0) return { unitType, unitId };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
|
|
382
423
|
export function uncheckTaskInPlan(basePath: string, mid: string, sid: string, tid: string): boolean {
|
|
383
424
|
const slicePath = resolveSlicePath(basePath, mid, sid);
|
|
384
425
|
if (!slicePath) return false;
|
|
@@ -244,6 +244,14 @@ export function formatFailureContext(result: VerificationResult): string {
|
|
|
244
244
|
return header + body;
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
export function formatFailureSignature(result: VerificationResult): string {
|
|
248
|
+
return result.checks
|
|
249
|
+
.filter((check) => check.exitCode !== 0)
|
|
250
|
+
.map((check) => `${check.command.trim()}#${check.exitCode}`)
|
|
251
|
+
.sort()
|
|
252
|
+
.join("\n");
|
|
253
|
+
}
|
|
254
|
+
|
|
247
255
|
// ─── Gate Execution ─────────────────────────────────────────────────────────
|
|
248
256
|
|
|
249
257
|
/** Characters that indicate shell control syntax when unquoted in a command string. */
|
|
@@ -414,9 +414,12 @@ function restoreArtifactProjections(basePath: string, manifest: StateManifest):
|
|
|
414
414
|
if (manifest.artifacts === undefined) return;
|
|
415
415
|
|
|
416
416
|
for (const artifact of manifest.artifacts) {
|
|
417
|
+
const content = artifact.full_content ?? "";
|
|
418
|
+
if (content === "") continue;
|
|
419
|
+
|
|
417
420
|
atomicWriteSync(
|
|
418
421
|
artifactProjectionPath(basePath, artifact.path),
|
|
419
|
-
|
|
422
|
+
content,
|
|
420
423
|
);
|
|
421
424
|
}
|
|
422
425
|
}
|
|
File without changes
|
|
File without changes
|