@opengsd/gsd-pi 1.1.1-dev.2034b16 → 1.1.1-dev.595401e
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/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +21 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +15 -6
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
- package/dist/resources/extensions/gsd/browser-evidence.js +29 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/dist/resources/extensions/gsd/doctor-runtime-checks.js +2 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.js +9 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +39 -0
- package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +40 -22
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/dist/resources/extensions/gsd/rule-registry.js +428 -52
- package/dist/resources/extensions/gsd/tools/validate-milestone.js +46 -16
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +29 -14
- package/dist/resources/extensions/gsd/verdict-parser.js +59 -15
- package/dist/rtk.d.ts +7 -1
- package/dist/rtk.js +27 -11
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- 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 +7 -7
- package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
- 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/dist/session/agent-session-compaction.d.ts +2 -0
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.d.ts.map +1 -1
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js +8 -2
- package/packages/gsd-agent-core/dist/session/agent-session-compaction.js.map +1 -1
- package/packages/gsd-agent-core/package.json +5 -5
- package/packages/gsd-agent-modes/package.json +7 -7
- package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
- package/packages/mcp-server/dist/remote-questions.js +23 -9
- package/packages/mcp-server/dist/remote-questions.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +1 -1
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- 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 +17 -17
- package/packages/pi-ai/dist/models.generated.js +19 -19
- package/packages/pi-ai/dist/models.generated.js.map +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 +1 -1
- package/packages/rpc-client/package.json +2 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +28 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +16 -6
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
- package/src/resources/extensions/gsd/browser-evidence.ts +26 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +8 -0
- package/src/resources/extensions/gsd/doctor-runtime-checks.ts +2 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +14 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +36 -0
- package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +40 -22
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +3 -3
- package/src/resources/extensions/gsd/rule-registry.ts +558 -58
- package/src/resources/extensions/gsd/rule-types.ts +2 -0
- package/src/resources/extensions/gsd/tests/browser-evidence.test.ts +142 -0
- package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +30 -0
- package/src/resources/extensions/gsd/tests/doctor-runtime-checks.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/integration/run-uat.test.ts +66 -10
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +157 -0
- package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/preferences.test.ts +29 -0
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +22 -1
- package/src/resources/extensions/gsd/tests/prompt-loader-extension-dir.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/rule-registry.test.ts +75 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-prompt-verification-classes.test.ts +6 -3
- package/src/resources/extensions/gsd/tests/validate-milestone-write-order.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +74 -0
- package/src/resources/extensions/gsd/tools/validate-milestone.ts +46 -15
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +31 -14
- package/src/resources/extensions/gsd/types.ts +63 -0
- package/src/resources/extensions/gsd/verdict-parser.ts +54 -13
- /package/dist/web/standalone/.next/static/{StOMnvtgGiBHrBOZJZ1Gr → IDKjyRHLIaumjgonPcYiX}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{StOMnvtgGiBHrBOZJZ1Gr → IDKjyRHLIaumjgonPcYiX}/_ssgManifest.js +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gsd/pi-coding-agent",
|
|
3
|
-
"version": "1.1.1-dev.
|
|
3
|
+
"version": "1.1.1-dev.595401e",
|
|
4
4
|
"description": "Coding agent CLI (vendored from earendil-works/pi)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"gsd": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"copy-assets": "node scripts/copy-assets.cjs"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opengsd/contracts": "^1.1.1-dev.
|
|
36
|
+
"@opengsd/contracts": "^1.1.1-dev.595401e",
|
|
37
37
|
"@mariozechner/jiti": "^2.6.2",
|
|
38
38
|
"@silvia-odwyer/photon-node": "0.3.4",
|
|
39
39
|
"chalk": "5.6.2",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"typebox": "1.1.38",
|
|
54
54
|
"undici": "7.26.0",
|
|
55
55
|
"yaml": "2.9.0",
|
|
56
|
-
"@gsd/agent-core": "^1.1.1-dev.
|
|
57
|
-
"@gsd/native": "^1.1.1-dev.
|
|
58
|
-
"@gsd/pi-agent-core": "^1.1.1-dev.
|
|
59
|
-
"@gsd/pi-ai": "^1.1.1-dev.
|
|
60
|
-
"@gsd/pi-tui": "^1.1.1-dev.
|
|
56
|
+
"@gsd/agent-core": "^1.1.1-dev.595401e",
|
|
57
|
+
"@gsd/native": "^1.1.1-dev.595401e",
|
|
58
|
+
"@gsd/pi-agent-core": "^1.1.1-dev.595401e",
|
|
59
|
+
"@gsd/pi-ai": "^1.1.1-dev.595401e",
|
|
60
|
+
"@gsd/pi-tui": "^1.1.1-dev.595401e",
|
|
61
61
|
"@sinclair/typebox": "^0.34.41"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/rpc-client",
|
|
3
|
-
"version": "1.1.1-dev.
|
|
3
|
+
"version": "1.1.1-dev.595401e",
|
|
4
4
|
"description": "Standalone RPC client SDK for GSD — zero internal dependencies",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"gsd": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test": "node --test dist/rpc-client.test.js"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@opengsd/contracts": "^1.1.1-dev.
|
|
37
|
+
"@opengsd/contracts": "^1.1.1-dev.595401e"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=22.0.0"
|
package/pkg/package.json
CHANGED
|
@@ -55,8 +55,10 @@ import { parseRoadmap as parseLegacyRoadmap } from "./parsers-legacy.js";
|
|
|
55
55
|
import { consumeSignal } from "./session-status-io.js";
|
|
56
56
|
import {
|
|
57
57
|
checkPostUnitHooks,
|
|
58
|
+
consumeHookFailure,
|
|
58
59
|
isRetryPending,
|
|
59
60
|
consumeRetryTrigger,
|
|
61
|
+
consumeGateBlock,
|
|
60
62
|
persistHookState,
|
|
61
63
|
resolveHookArtifactPath,
|
|
62
64
|
} from "./post-unit-hooks.js";
|
|
@@ -2227,11 +2229,11 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
2227
2229
|
// ── Post-unit hooks ──
|
|
2228
2230
|
if (s.currentUnit && !s.stepMode) {
|
|
2229
2231
|
const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
2232
|
+
persistHookState(s.basePath);
|
|
2230
2233
|
if (hookUnit) {
|
|
2231
2234
|
if (s.currentUnit) {
|
|
2232
2235
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
2233
2236
|
}
|
|
2234
|
-
persistHookState(s.basePath);
|
|
2235
2237
|
|
|
2236
2238
|
return enqueueSidecar(
|
|
2237
2239
|
s, ctx,
|
|
@@ -2240,12 +2242,23 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
2240
2242
|
);
|
|
2241
2243
|
}
|
|
2242
2244
|
|
|
2245
|
+
const hookFailure = consumeHookFailure();
|
|
2246
|
+
if (hookFailure) {
|
|
2247
|
+
ctx.ui.notify(
|
|
2248
|
+
`Post-unit hook ${hookFailure.hookName} failed for ${hookFailure.unitId}: ${hookFailure.reason}. Pausing auto-mode.`,
|
|
2249
|
+
"warning",
|
|
2250
|
+
);
|
|
2251
|
+
await pauseAuto(ctx, pi);
|
|
2252
|
+
return "stopped";
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2243
2255
|
// Check if a hook requested a retry of the trigger unit
|
|
2244
2256
|
if (isRetryPending()) {
|
|
2245
2257
|
const trigger = consumeRetryTrigger();
|
|
2246
2258
|
if (trigger) {
|
|
2259
|
+
persistHookState(s.basePath);
|
|
2247
2260
|
ctx.ui.notify(
|
|
2248
|
-
`Hook requested retry of ${trigger.unitType} ${trigger.unitId} — resetting
|
|
2261
|
+
`Hook requested retry of ${trigger.unitType} ${trigger.unitId} — resetting trigger unit state.`,
|
|
2249
2262
|
"info",
|
|
2250
2263
|
);
|
|
2251
2264
|
|
|
@@ -2299,6 +2312,19 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
2299
2312
|
// Fall through to normal dispatch — deriveState will re-derive the unit
|
|
2300
2313
|
}
|
|
2301
2314
|
}
|
|
2315
|
+
|
|
2316
|
+
const gateBlock = consumeGateBlock();
|
|
2317
|
+
if (gateBlock) {
|
|
2318
|
+
persistHookState(s.basePath);
|
|
2319
|
+
const verdict = gateBlock.verdict ? ` verdict=${gateBlock.verdict};` : "";
|
|
2320
|
+
const artifact = gateBlock.artifact ? ` artifact=${gateBlock.artifact};` : "";
|
|
2321
|
+
const message =
|
|
2322
|
+
`Post-unit gate "${gateBlock.hookName}" blocked ${gateBlock.triggerUnitType} ${gateBlock.triggerUnitId}:` +
|
|
2323
|
+
`${verdict}${artifact} ${gateBlock.reason}. Run /gsd status to inspect, then /gsd auto after recovery.`;
|
|
2324
|
+
ctx.ui.notify(message, "warning");
|
|
2325
|
+
await pauseAuto(ctx, pi);
|
|
2326
|
+
return "stopped";
|
|
2327
|
+
}
|
|
2302
2328
|
}
|
|
2303
2329
|
|
|
2304
2330
|
// ── Fast-path stop detection (#3487) ──
|
|
@@ -1429,7 +1429,7 @@ export async function checkNeedsRunUat(
|
|
|
1429
1429
|
// If the UAT file already contains a verdict, UAT has been run — skip
|
|
1430
1430
|
if (hasVerdict(uatContent)) continue;
|
|
1431
1431
|
// Also check the ASSESSMENT file — the run-uat prompt writes the verdict
|
|
1432
|
-
// there (via
|
|
1432
|
+
// there (via gsd_uat_result_save), not into the
|
|
1433
1433
|
// UAT spec file. Without this check the unit re-dispatches indefinitely.
|
|
1434
1434
|
const assessmentFile = resolveSliceFile(base, mid, sid, "ASSESSMENT");
|
|
1435
1435
|
if (assessmentFile) {
|
|
@@ -2986,13 +2986,23 @@ export async function buildValidateMilestonePrompt(
|
|
|
2986
2986
|
if (isDbAvailable()) {
|
|
2987
2987
|
const milestone = getMilestone(mid);
|
|
2988
2988
|
if (milestone) {
|
|
2989
|
+
const escapeCell = (value: string) =>
|
|
2990
|
+
value.replace(/[\\|]/g, (char) => `\\${char}`).replace(/\r?\n/g, " ");
|
|
2989
2991
|
const classes: string[] = [];
|
|
2990
|
-
if (milestone.verification_contract) classes.push(
|
|
2991
|
-
if (milestone.verification_integration) classes.push(
|
|
2992
|
-
if (milestone.verification_operational) classes.push(
|
|
2993
|
-
if (milestone.verification_uat) classes.push(
|
|
2992
|
+
if (milestone.verification_contract) classes.push(`| Contract | ${escapeCell(milestone.verification_contract)} |`);
|
|
2993
|
+
if (milestone.verification_integration) classes.push(`| Integration | ${escapeCell(milestone.verification_integration)} |`);
|
|
2994
|
+
if (milestone.verification_operational) classes.push(`| Operational | ${escapeCell(milestone.verification_operational)} |`);
|
|
2995
|
+
if (milestone.verification_uat) classes.push(`| UAT | ${escapeCell(milestone.verification_uat)} |`);
|
|
2994
2996
|
if (classes.length > 0) {
|
|
2995
|
-
const verificationClasses =
|
|
2997
|
+
const verificationClasses = [
|
|
2998
|
+
"### Verification Classes (from planning)",
|
|
2999
|
+
"",
|
|
3000
|
+
"These verification tiers were defined during milestone planning. Every row in this table must appear in `verificationClasses` with the same canonical class name.",
|
|
3001
|
+
"",
|
|
3002
|
+
"| Class | Planned Check |",
|
|
3003
|
+
"| --- | --- |",
|
|
3004
|
+
...classes,
|
|
3005
|
+
].join("\n");
|
|
2996
3006
|
inlined.push(verificationClasses);
|
|
2997
3007
|
trackPromptContext(contextTelemetry, "verification-classes", "inline", verificationClasses);
|
|
2998
3008
|
}
|
|
@@ -1095,7 +1095,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
1095
1095
|
promptGuidelines: [
|
|
1096
1096
|
"Use gsd_validate_milestone when all slices are done and the milestone needs validation before completion.",
|
|
1097
1097
|
"Parameters: milestoneId, verdict, remediationRound, successCriteriaChecklist, sliceDeliveryAudit, crossSliceIntegration, requirementCoverage, verificationClasses (optional), verdictRationale, remediationPlan (optional).",
|
|
1098
|
-
"If verification classes were planned, verificationClasses must
|
|
1098
|
+
"If verification classes were planned, verificationClasses must be a complete canonical table with one row for every applicable planned class using the exact class names Contract, Integration, Operational, and UAT. Do not submit a partial table.",
|
|
1099
1099
|
"Planned verification text marked as none/not required/not applicable/N/A (including suffixed variants such as 'not required - backend-only') is treated as not applicable and does not require a class row.",
|
|
1100
1100
|
"If verdict is 'needs-remediation', also provide remediationPlan and use gsd_reassess_roadmap to add remediation slices to the roadmap.",
|
|
1101
1101
|
"On success, returns validationPath where VALIDATION.md was written.",
|
|
@@ -1108,7 +1108,7 @@ export function registerDbTools(pi: ExtensionAPI): void {
|
|
|
1108
1108
|
sliceDeliveryAudit: Type.String({ description: "Markdown table auditing each slice's claimed vs delivered output" }),
|
|
1109
1109
|
crossSliceIntegration: Type.String({ description: "Markdown describing any cross-slice boundary mismatches" }),
|
|
1110
1110
|
requirementCoverage: Type.String({ description: "Markdown describing any unaddressed requirements" }),
|
|
1111
|
-
verificationClasses: Type.Optional(Type.String({ description: "
|
|
1111
|
+
verificationClasses: Type.Optional(Type.String({ description: "Complete markdown table describing verification class compliance and gaps; include one canonical row for every applicable planned class (Contract, Integration, Operational, UAT)" })),
|
|
1112
1112
|
verdictRationale: Type.String({ description: "Why this verdict was chosen" }),
|
|
1113
1113
|
remediationPlan: Type.Optional(Type.String({ description: "Remediation plan (required if verdict is needs-remediation)" })),
|
|
1114
1114
|
}),
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// Project/App: gsd-pi
|
|
2
2
|
// File Purpose: Shared browser-observable UAT requirement and evidence detection.
|
|
3
3
|
|
|
4
|
-
export const BROWSER_REQUIREMENT_RE = /\b(?:
|
|
4
|
+
export const BROWSER_REQUIREMENT_RE = /\b(?:file:\/\/|localhost|playwright|chrome|screenshot|snapshot|browser_(?:assert|batch|find|verify|snapshot_refs))\b|\b(?:open|launch|navigate|load|visit|serve|start)\b.{0,80}\b(?:browser|page|localhost|file:\/\/)\b|\bbrowser\s+(?:check|session|test|uat|tool|automation|interaction|flow)\b/i;
|
|
5
5
|
export const NO_BROWSER_EVIDENCE_RE = /\b(?:no|without|not|wasn'?t|isn'?t)\s+(?:automated\s+)?(?:live\s+)?browser(?:\s+(?:session|test|uat))?|\bno\s+automated\s+browser\b|\bnot\s+conducted\b/i;
|
|
6
6
|
export const BROWSER_RUNTIME_RE = /\b(?:browser|playwright|chrome|camoufox|browser_(?:assert|batch|find|verify|snapshot_refs)|screenshot|snapshot|file:\/\/|localhost)\b/i;
|
|
7
7
|
export const BROWSER_ACTION_RE = /\b(?:open(?:ed)?|navigate(?:d)?|click(?:ed)?|type(?:d)?|reload(?:ed)?|capture(?:d)?|screenshot|snapshot)\b/i;
|
|
8
8
|
export const BROWSER_ASSERTION_RE = /\b(?:assert(?:ed|ion)?|observed|confirmed|verified|expected|visible|text|count|label|strikethrough|localstorage|screenshot|snapshot|passed)\b/i;
|
|
9
|
+
const NON_REQUIREMENT_BROWSER_HEADING_RE = /^(?:not\s+proven|not\s+covered|out\s+of\s+scope|deferred|follow-?ups?|known\s+limitations|notes\s+for\s+tester)\b/i;
|
|
10
|
+
const NON_REQUIREMENT_BROWSER_LINE_RE = /\b(?:deferred|not\s+proven|not\s+covered|out\s+of\s+scope|future\s+slice|follow-?up|no\s+(?:live\s+)?browser|without\s+(?:a\s+)?browser|not\s+(?:a\s+)?browser)\b/i;
|
|
9
11
|
|
|
10
12
|
export function compactTextParts(parts: Array<string | string[] | null | undefined>): string {
|
|
11
13
|
return parts.flatMap((part) => Array.isArray(part) ? part : [part])
|
|
@@ -14,7 +16,29 @@ export function compactTextParts(parts: Array<string | string[] | null | undefin
|
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function hasBrowserRequiredText(text: string): boolean {
|
|
17
|
-
|
|
19
|
+
let inNonRequirementSection = false;
|
|
20
|
+
let nonRequirementDepth = 0;
|
|
21
|
+
for (const line of text.split(/\r?\n/)) {
|
|
22
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+?)\s*$/);
|
|
23
|
+
if (headingMatch) {
|
|
24
|
+
const depth = headingMatch[1]!.length;
|
|
25
|
+
const title = headingMatch[2] ?? "";
|
|
26
|
+
// Only update section context when at the same or higher level than the
|
|
27
|
+
// heading that opened the non-requirement zone. A sub-heading deeper than
|
|
28
|
+
// the opening heading must not escape or re-enter the zone on its own.
|
|
29
|
+
if (!inNonRequirementSection || depth <= nonRequirementDepth) {
|
|
30
|
+
inNonRequirementSection = NON_REQUIREMENT_BROWSER_HEADING_RE.test(title);
|
|
31
|
+
nonRequirementDepth = inNonRequirementSection ? depth : 0;
|
|
32
|
+
}
|
|
33
|
+
// Check the heading title itself — section state is already updated, so
|
|
34
|
+
// we correctly skip headings that opened a non-requirement zone.
|
|
35
|
+
if (!inNonRequirementSection && BROWSER_REQUIREMENT_RE.test(title)) return true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (inNonRequirementSection || NON_REQUIREMENT_BROWSER_LINE_RE.test(line)) continue;
|
|
39
|
+
if (BROWSER_REQUIREMENT_RE.test(line)) return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
18
42
|
}
|
|
19
43
|
|
|
20
44
|
export function hasBrowserEvidenceText(text: string): boolean {
|
|
@@ -305,10 +305,18 @@ This config sets a parent workspace with two child repositories. The implicit `p
|
|
|
305
305
|
- `max_cycles`: number — max times this hook fires per trigger (default: 1, max: 10).
|
|
306
306
|
- `model`: string — optional model override.
|
|
307
307
|
- `artifact`: string — expected output file name (relative to task/slice dir). Hook is skipped if file already exists (idempotent).
|
|
308
|
+
- `criticality`: `"advisory"` or `"blocking"` — advisory preserves current best-effort behavior; blocking requires clean hook completion plus a valid outcome verdict before auto-mode advances. Default: `"advisory"`.
|
|
308
309
|
- `retry_on`: string — if this file is produced instead of the artifact, re-run the trigger unit then re-run hooks.
|
|
310
|
+
- `on_block`: object — optional routing for blocking findings:
|
|
311
|
+
- `action`: `"retry-unit"`, `"retry-task"`, `"queue-task"`, `"queue-slice"`, or `"pause"`.
|
|
312
|
+
- `artifact`: string — optional compatibility artifact for retry routing.
|
|
309
313
|
- `agent`: string — agent definition file to use for hook execution.
|
|
310
314
|
- `enabled`: boolean — toggle without removing (default: `true`).
|
|
311
315
|
|
|
316
|
+
Blocking hook artifacts must begin with YAML frontmatter containing either `verdict` or `outcome.verdict`.
|
|
317
|
+
Supported verdicts are `pass`, `advisory`, `needs-rework`, `needs-remediation`, and `needs-attention`.
|
|
318
|
+
`pass` and `advisory` continue; `needs-rework` retries the trigger unit when routed with `retry-unit`/`retry-task`; `needs-remediation` and `needs-attention` pause with recovery guidance.
|
|
319
|
+
|
|
312
320
|
- `pre_dispatch_hooks`: array — hooks that fire before a unit is dispatched. Each entry has:
|
|
313
321
|
- `name`: string — unique hook identifier.
|
|
314
322
|
- `before`: string[] — unit types to intercept.
|
|
@@ -269,14 +269,14 @@ export async function checkRuntimeHealth(
|
|
|
269
269
|
} catch {
|
|
270
270
|
count = MAX_UAT_ATTEMPTS + 1;
|
|
271
271
|
}
|
|
272
|
-
if (count
|
|
272
|
+
if (count < MAX_UAT_ATTEMPTS) continue;
|
|
273
273
|
|
|
274
274
|
issues.push({
|
|
275
275
|
severity: "warning",
|
|
276
276
|
code: "uat_retry_exhausted",
|
|
277
277
|
scope: "slice",
|
|
278
278
|
unitId: `${mid}/${sid}`,
|
|
279
|
-
message: `run-uat for ${mid}/${sid} exhausted ${count
|
|
279
|
+
message: `run-uat for ${mid}/${sid} exhausted ${count} attempt(s) without an ASSESSMENT verdict. Reset the retry counter after fixing the underlying UAT/tool issue, then rerun /gsd auto.`,
|
|
280
280
|
file: `.gsd/runtime/${fileName}`,
|
|
281
281
|
fixable: true,
|
|
282
282
|
});
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
HookDispatchResult,
|
|
10
10
|
PreDispatchResult,
|
|
11
11
|
HookStatusEntry,
|
|
12
|
+
PostUnitGateBlock,
|
|
12
13
|
} from "./types.js";
|
|
13
14
|
import { getOrCreateRegistry, resolveHookArtifactPath } from "./rule-registry.js";
|
|
14
15
|
|
|
@@ -33,10 +34,22 @@ export function isRetryPending(): boolean {
|
|
|
33
34
|
return getOrCreateRegistry().isRetryPending();
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
export function consumeRetryTrigger(): { unitType: string; unitId: string; retryArtifact
|
|
37
|
+
export function consumeRetryTrigger(): { unitType: string; unitId: string; retryArtifact?: string } | null {
|
|
37
38
|
return getOrCreateRegistry().consumeRetryTrigger();
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
export function consumeHookFailure(): { hookName: string; unitType: string; unitId: string; reason: string } | null {
|
|
42
|
+
return getOrCreateRegistry().consumeHookFailure();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function isGateBlockPending(): boolean {
|
|
46
|
+
return getOrCreateRegistry().isGateBlockPending();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function consumeGateBlock(): PostUnitGateBlock | null {
|
|
50
|
+
return getOrCreateRegistry().consumeGateBlock();
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
export function resetHookState(): void {
|
|
41
54
|
getOrCreateRegistry().resetState();
|
|
42
55
|
}
|
|
@@ -29,6 +29,14 @@ const VALID_UOK_TURN_ACTIONS = new Set<"commit" | "snapshot" | "status-only">([
|
|
|
29
29
|
"snapshot",
|
|
30
30
|
"status-only",
|
|
31
31
|
]);
|
|
32
|
+
const VALID_POST_UNIT_HOOK_CRITICALITIES = new Set(["advisory", "blocking"]);
|
|
33
|
+
const VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS = new Set([
|
|
34
|
+
"retry-unit",
|
|
35
|
+
"retry-task",
|
|
36
|
+
"queue-task",
|
|
37
|
+
"queue-slice",
|
|
38
|
+
"pause",
|
|
39
|
+
]);
|
|
32
40
|
|
|
33
41
|
export function validatePreferences(preferences: GSDPreferences): {
|
|
34
42
|
preferences: GSDPreferences;
|
|
@@ -486,9 +494,37 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
486
494
|
if (typeof hook.artifact === "string" && hook.artifact.trim()) {
|
|
487
495
|
validHook.artifact = hook.artifact.trim();
|
|
488
496
|
}
|
|
497
|
+
if (hook.criticality !== undefined) {
|
|
498
|
+
const criticality = typeof hook.criticality === "string" ? hook.criticality.trim() : "";
|
|
499
|
+
if (VALID_POST_UNIT_HOOK_CRITICALITIES.has(criticality)) {
|
|
500
|
+
validHook.criticality = criticality as PostUnitHookConfig["criticality"];
|
|
501
|
+
} else {
|
|
502
|
+
errors.push(`post_unit_hooks "${name}" invalid criticality: ${String(hook.criticality)}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
489
505
|
if (typeof hook.retry_on === "string" && hook.retry_on.trim()) {
|
|
490
506
|
validHook.retry_on = hook.retry_on.trim();
|
|
491
507
|
}
|
|
508
|
+
if (hook.on_block !== undefined) {
|
|
509
|
+
if (!hook.on_block || typeof hook.on_block !== "object") {
|
|
510
|
+
errors.push(`post_unit_hooks "${name}" on_block must be an object`);
|
|
511
|
+
} else {
|
|
512
|
+
const onBlock = hook.on_block as unknown as Record<string, unknown>;
|
|
513
|
+
const action = typeof onBlock.action === "string" ? onBlock.action.trim() : "";
|
|
514
|
+
if (!VALID_POST_UNIT_HOOK_ON_BLOCK_ACTIONS.has(action)) {
|
|
515
|
+
errors.push(`post_unit_hooks "${name}" invalid on_block action: ${String(onBlock.action)}`);
|
|
516
|
+
} else {
|
|
517
|
+
validHook.on_block = { action: action as NonNullable<PostUnitHookConfig["on_block"]>["action"] };
|
|
518
|
+
if (typeof onBlock.artifact === "string" && onBlock.artifact.trim()) {
|
|
519
|
+
validHook.on_block.artifact = onBlock.artifact.trim();
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (validHook.criticality === "blocking" && !validHook.artifact) {
|
|
525
|
+
errors.push(`post_unit_hooks "${name}" criticality blocking requires artifact`);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
492
528
|
if (typeof hook.agent === "string" && hook.agent.trim()) {
|
|
493
529
|
validHook.agent = hook.agent.trim();
|
|
494
530
|
}
|
|
@@ -33,6 +33,10 @@ function hasRequiredExtensionAssets(rootDir: string, exists: ExistsFn = existsSy
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function isSourceExtensionDir(moduleDir: string): boolean {
|
|
37
|
+
return moduleDir.replaceAll("\\", "/").endsWith("/src/resources/extensions/gsd");
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
export function resolveExtensionDirFromCandidates(
|
|
37
41
|
moduleDir: string,
|
|
38
42
|
agentGsdDir: string,
|
|
@@ -41,6 +45,10 @@ export function resolveExtensionDirFromCandidates(
|
|
|
41
45
|
const moduleUsable = hasRequiredExtensionAssets(moduleDir, exists);
|
|
42
46
|
const agentUsable = hasRequiredExtensionAssets(agentGsdDir, exists);
|
|
43
47
|
|
|
48
|
+
// Source checkouts must use their own prompt tree. Otherwise local tests and
|
|
49
|
+
// dev runs can silently render stale prompts from ~/.gsd/agent/extensions/gsd.
|
|
50
|
+
if (moduleUsable && isSourceExtensionDir(moduleDir)) return moduleDir;
|
|
51
|
+
|
|
44
52
|
// Prefer the user-local extension tree when both are valid. This avoids
|
|
45
53
|
// leaking npm/global-install paths into prompts on Windows.
|
|
46
54
|
if (agentUsable) return agentGsdDir;
|
|
@@ -63,35 +63,53 @@ After running all checks, compute the **overall verdict**:
|
|
|
63
63
|
- `FAIL` — one or more automatable checks failed
|
|
64
64
|
- `PARTIAL` — one or more automatable checks were skipped or returned inconclusive results (not the same as `NEEDS-HUMAN` — use PARTIAL only when the agent itself could not determine pass/fail for a check it was supposed to automate)
|
|
65
65
|
|
|
66
|
-
Call `
|
|
66
|
+
Call `gsd_uat_result_save` once after all checks are complete. The tool computes the assessment path, persists to DB/disk, saves attempt history, and saves the aggregate UAT gate.
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
sliceId: {{sliceId}}
|
|
71
|
-
uatType: {{uatType}}
|
|
72
|
-
verdict: PASS | FAIL | PARTIAL
|
|
73
|
-
date: <ISO 8601 timestamp>
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
# UAT Result — {{sliceId}}
|
|
77
|
-
|
|
78
|
-
## Checks
|
|
68
|
+
Pass these top-level fields:
|
|
79
69
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
70
|
+
```ts
|
|
71
|
+
milestoneId: "{{milestoneId}}",
|
|
72
|
+
sliceId: "{{sliceId}}",
|
|
73
|
+
uatType: "{{uatType}}",
|
|
74
|
+
verdict: "PASS" | "FAIL" | "PARTIAL",
|
|
75
|
+
notes: "<one sentence overall verdict rationale>",
|
|
76
|
+
```
|
|
87
77
|
|
|
88
|
-
|
|
78
|
+
Use this exact `presentation` shape in the save call so the audit can verify the run-uat tool surface without retrying missing fields one by one:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
presentation: {
|
|
82
|
+
surface: "mcp",
|
|
83
|
+
presentedTools: [
|
|
84
|
+
"gsd_uat_exec",
|
|
85
|
+
"gsd_uat_result_save",
|
|
86
|
+
"gsd_resume",
|
|
87
|
+
"gsd_milestone_status",
|
|
88
|
+
"gsd_journal_query",
|
|
89
|
+
],
|
|
90
|
+
blockedTools: [
|
|
91
|
+
{ name: "gsd_exec", reason: "forbidden during run-uat" },
|
|
92
|
+
{ name: "gsd_summary_save", reason: "forbidden during run-uat" },
|
|
93
|
+
{ name: "gsd_save_gate_result", reason: "forbidden during run-uat" },
|
|
94
|
+
],
|
|
95
|
+
}
|
|
96
|
+
```
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
Pass `checks` with this logical shape:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
checks: [{
|
|
102
|
+
id: "<stable check id>",
|
|
103
|
+
description: "<check description from the UAT file>",
|
|
104
|
+
mode: "artifact" | "runtime" | "browser" | "human-follow-up",
|
|
105
|
+
result: "PASS" | "FAIL" | "NEEDS-HUMAN",
|
|
106
|
+
evidence: [{ kind: "gsd_uat_exec", ref: "<evidence id>" }],
|
|
107
|
+
notes: "<observed output, evidence, reason, or manual follow-up>",
|
|
108
|
+
}]
|
|
91
109
|
```
|
|
92
110
|
|
|
93
111
|
---
|
|
94
112
|
|
|
95
|
-
**You MUST call `
|
|
113
|
+
**You MUST call `gsd_uat_result_save` before finishing. Do not write the assessment file directly, and do not call `gsd_summary_save` as a substitute.**
|
|
96
114
|
|
|
97
115
|
When done, say: "UAT {{sliceId}} complete."
|
|
@@ -33,7 +33,7 @@ Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directo
|
|
|
33
33
|
Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
|
|
34
34
|
|
|
35
35
|
**Reviewer C - Assessment & Acceptance Criteria**
|
|
36
|
-
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md` for criteria. Check slice SUMMARY and ASSESSMENT files under `.gsd/milestones/{{milestoneId}}/slices/`; UAT files are specs, not evidence. Verify each criterion maps to passing evidence. Then review inlined
|
|
36
|
+
Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md` for criteria. Check slice SUMMARY and ASSESSMENT files under `.gsd/milestones/{{milestoneId}}/slices/`; UAT files are specs, not evidence. Verify each criterion maps to passing evidence. Then review the inlined `Verification Classes (from planning)` table. For every planned row in that table, output a `Verification Classes` table with columns `Class | Planned Check | Evidence | Verdict`. Preserve every planned non-empty class row; do not summarize, rename, combine, or omit planned classes. The first cell of each row must be exactly `Contract`, `Integration`, `Operational`, or `UAT` when that class is present in planning. If a planned class lacks evidence, still include its canonical row and mark the verdict NEEDS-ATTENTION or FAIL. If a planned browser/UAT class has no ASSESSMENT with browser/runtime actions and assertions, return NEEDS-ATTENTION. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered by evidence, NEEDS-ATTENTION if gaps exist."
|
|
37
37
|
|
|
38
38
|
### Step 2 - Synthesize Findings
|
|
39
39
|
|
|
@@ -71,8 +71,8 @@ reviewers: 3
|
|
|
71
71
|
<if verdict is not pass: specific actions required>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Call `gsd_validate_milestone` with the camelCase fields `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` when needed. If
|
|
75
|
-
|
|
74
|
+
Call `gsd_validate_milestone` with the camelCase fields `milestoneId`, `verdict`, `remediationRound`, `successCriteriaChecklist`, `sliceDeliveryAudit`, `crossSliceIntegration`, `requirementCoverage`, `verdictRationale`, and `remediationPlan` when needed. If planning included verification classes, pass a complete canonical table in `verificationClasses`.
|
|
75
|
+
Set `verificationClasses` to the `Verification Classes` subsection from Reviewer C. It must include one canonical row for every non-empty planned class from `Verification Classes (from planning)`: `Contract`, `Integration`, `Operational`, and/or `UAT`. If Reviewer C omitted a planned class, reconstruct the missing row from the planning table, set Evidence to the gap, and use NEEDS-ATTENTION or FAIL. Do not call `gsd_validate_milestone` with a partial `verificationClasses` table.
|
|
76
76
|
|
|
77
77
|
**DB access safety:** Do NOT query `.gsd/gsd.db` directly via `sqlite3` or `node -e require('better-sqlite3')` - the engine owns the WAL connection. Use `gsd_milestone_status` for milestone and slice state. Data is already inlined or available via `gsd_*` tools. Direct DB access risks WAL corruption and bypasses validation.
|
|
78
78
|
|