gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.593fa74
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 +34 -1
- package/dist/cli.js +17 -0
- package/dist/mcp-server.js +37 -14
- package/dist/resources/agents/debugger.md +58 -0
- package/dist/resources/agents/doc-writer.md +43 -0
- package/dist/resources/agents/git-ops.md +56 -0
- package/dist/resources/agents/javascript-pro.md +46 -271
- package/dist/resources/agents/planner.md +55 -0
- package/dist/resources/agents/refactorer.md +47 -0
- package/dist/resources/agents/reviewer.md +48 -0
- package/dist/resources/agents/security.md +59 -0
- package/dist/resources/agents/tester.md +50 -0
- package/dist/resources/agents/typescript-pro.md +41 -235
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +40 -12
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +103 -6
- package/dist/resources/extensions/gsd/auto/phases.js +4 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
- package/dist/resources/extensions/gsd/auto-start.js +24 -4
- package/dist/resources/extensions/gsd/auto.js +4 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
- package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
- package/dist/resources/extensions/gsd/error-classifier.js +4 -1
- package/dist/resources/extensions/gsd/gate-registry.js +208 -0
- package/dist/resources/extensions/gsd/gsd-db.js +41 -0
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
- package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
- package/dist/resources/extensions/gsd/notification-store.js +5 -4
- package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
- package/dist/resources/extensions/gsd/state.js +9 -2
- package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
- package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
- package/dist/resources/extensions/ollama/index.js +13 -5
- package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
- package/dist/resources/extensions/subagent/agents.js +8 -0
- package/dist/resources/extensions/subagent/index.js +17 -0
- package/dist/startup-model-validation.d.ts +0 -1
- package/dist/startup-model-validation.js +6 -2
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
- 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 +13 -13
- 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/mcp-server/dist/server.d.ts +12 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +90 -42
- package/packages/mcp-server/dist/server.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/src/server.ts +110 -38
- package/packages/mcp-server/src/workflow-tools.ts +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +87 -12
- package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
- package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
- package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +72 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
- package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +84 -12
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
- package/pkg/package.json +1 -1
- package/src/resources/agents/debugger.md +58 -0
- package/src/resources/agents/doc-writer.md +43 -0
- package/src/resources/agents/git-ops.md +56 -0
- package/src/resources/agents/javascript-pro.md +46 -271
- package/src/resources/agents/planner.md +55 -0
- package/src/resources/agents/refactorer.md +47 -0
- package/src/resources/agents/reviewer.md +48 -0
- package/src/resources/agents/security.md +59 -0
- package/src/resources/agents/tester.md +50 -0
- package/src/resources/agents/typescript-pro.md +41 -235
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +45 -12
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +109 -3
- package/src/resources/extensions/claude-code-cli/tests/partial-builder.test.ts +91 -2
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +133 -2
- package/src/resources/extensions/gsd/auto/phases.ts +4 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
- package/src/resources/extensions/gsd/auto-start.ts +31 -4
- package/src/resources/extensions/gsd/auto.ts +4 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
- package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
- package/src/resources/extensions/gsd/error-classifier.ts +4 -1
- package/src/resources/extensions/gsd/gate-registry.ts +251 -0
- package/src/resources/extensions/gsd/gsd-db.ts +51 -0
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
- package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
- package/src/resources/extensions/gsd/notification-store.ts +5 -4
- package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
- package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
- package/src/resources/extensions/gsd/state.ts +13 -2
- package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
- package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
- package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
- package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
- package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
- package/src/resources/extensions/gsd/types.ts +26 -0
- package/src/resources/extensions/ollama/index.ts +13 -3
- package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
- package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
- package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
- package/src/resources/extensions/subagent/agents.ts +10 -0
- package/src/resources/extensions/subagent/index.ts +18 -0
- package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → h8B07q4xc-ujHRD7esO6O}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → h8B07q4xc-ujHRD7esO6O}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Gate Registry — single source of truth for quality-gate ownership.
|
|
3
|
+
*
|
|
4
|
+
* Each gate declares which workflow turn owns it, the scope at which it is
|
|
5
|
+
* persisted in the `quality_gates` table, and the question/guidance text used
|
|
6
|
+
* in the prompt that turn sends. The registry replaces the ad-hoc
|
|
7
|
+
* `GATE_QUESTIONS` table that used to live in `auto-prompts.ts`, and every
|
|
8
|
+
* layer of the prompt system (prompt builders, dispatch rules, state
|
|
9
|
+
* derivation, tool handlers) consults it so a pending gate can never be
|
|
10
|
+
* silently dropped.
|
|
11
|
+
*
|
|
12
|
+
* Design notes:
|
|
13
|
+
* - `GATE_REGISTRY` is exhaustiveness-checked against `GateId` via
|
|
14
|
+
* `satisfies Record<GateId, GateDefinition>`, so adding a new GateId
|
|
15
|
+
* without a registry entry is a compile error.
|
|
16
|
+
* - `getGatesForTurn(turn)` returns the definitions a turn owns.
|
|
17
|
+
* - `assertGateCoverage(pending, turn)` throws a GSDError if the pending
|
|
18
|
+
* list for a turn contains unknown gates, or if any gate owned by the
|
|
19
|
+
* turn is missing from the pending list.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { GSDError, GSD_PARSE_ERROR } from "./errors.js";
|
|
23
|
+
import type { GateId, GateRow, GateScope } from "./types.js";
|
|
24
|
+
|
|
25
|
+
/** Which workflow turn is responsible for evaluating / closing a gate. */
|
|
26
|
+
export type OwnerTurn =
|
|
27
|
+
| "gate-evaluate"
|
|
28
|
+
| "execute-task"
|
|
29
|
+
| "complete-slice"
|
|
30
|
+
| "validate-milestone";
|
|
31
|
+
|
|
32
|
+
export interface GateDefinition {
|
|
33
|
+
id: GateId;
|
|
34
|
+
scope: GateScope;
|
|
35
|
+
ownerTurn: OwnerTurn;
|
|
36
|
+
/** One-line question the assistant must answer. */
|
|
37
|
+
question: string;
|
|
38
|
+
/** Markdown guidance describing what a good answer looks like. */
|
|
39
|
+
guidance: string;
|
|
40
|
+
/** H3 section header used in the artifact the turn writes
|
|
41
|
+
* (e.g. "Operational Readiness" for Q8 in the slice summary). */
|
|
42
|
+
promptSection: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const GATE_REGISTRY = {
|
|
46
|
+
Q3: {
|
|
47
|
+
id: "Q3",
|
|
48
|
+
scope: "slice",
|
|
49
|
+
ownerTurn: "gate-evaluate",
|
|
50
|
+
question: "How can this be exploited?",
|
|
51
|
+
guidance: [
|
|
52
|
+
"Identify abuse scenarios: parameter tampering, replay attacks, privilege escalation.",
|
|
53
|
+
"Map data exposure risks: PII, tokens, secrets accessible through this slice.",
|
|
54
|
+
"Define input trust boundaries: untrusted user input reaching DB, API, or filesystem.",
|
|
55
|
+
"If none apply, return verdict 'omitted' with rationale explaining why.",
|
|
56
|
+
].join("\n"),
|
|
57
|
+
promptSection: "Abuse Surface",
|
|
58
|
+
},
|
|
59
|
+
Q4: {
|
|
60
|
+
id: "Q4",
|
|
61
|
+
scope: "slice",
|
|
62
|
+
ownerTurn: "gate-evaluate",
|
|
63
|
+
question: "What existing promises does this break?",
|
|
64
|
+
guidance: [
|
|
65
|
+
"List which existing requirements (R001, R003, etc.) are touched by this slice.",
|
|
66
|
+
"Identify what must be re-tested after shipping.",
|
|
67
|
+
"Flag decisions that should be revisited given the new scope.",
|
|
68
|
+
"If no existing requirements are affected, return verdict 'omitted'.",
|
|
69
|
+
].join("\n"),
|
|
70
|
+
promptSection: "Broken Promises",
|
|
71
|
+
},
|
|
72
|
+
Q5: {
|
|
73
|
+
id: "Q5",
|
|
74
|
+
scope: "task",
|
|
75
|
+
ownerTurn: "execute-task",
|
|
76
|
+
question: "What breaks when dependencies fail?",
|
|
77
|
+
guidance: [
|
|
78
|
+
"Enumerate the task's external dependencies (APIs, filesystem, network, subprocesses).",
|
|
79
|
+
"Describe the failure path for each: timeout, malformed response, connection loss.",
|
|
80
|
+
"Verify the implementation handles each failure or explicitly bubbles the error.",
|
|
81
|
+
"Return verdict 'omitted' only if the task has no external dependencies.",
|
|
82
|
+
].join("\n"),
|
|
83
|
+
promptSection: "Failure Modes",
|
|
84
|
+
},
|
|
85
|
+
Q6: {
|
|
86
|
+
id: "Q6",
|
|
87
|
+
scope: "task",
|
|
88
|
+
ownerTurn: "execute-task",
|
|
89
|
+
question: "What is the 10x load breakpoint?",
|
|
90
|
+
guidance: [
|
|
91
|
+
"Identify the resource that saturates first at 10x the expected load.",
|
|
92
|
+
"Describe the protection applied (pool sizing, rate limiting, pagination, caching).",
|
|
93
|
+
"Return verdict 'omitted' if the task has no runtime load dimension.",
|
|
94
|
+
].join("\n"),
|
|
95
|
+
promptSection: "Load Profile",
|
|
96
|
+
},
|
|
97
|
+
Q7: {
|
|
98
|
+
id: "Q7",
|
|
99
|
+
scope: "task",
|
|
100
|
+
ownerTurn: "execute-task",
|
|
101
|
+
question: "What negative tests protect this task?",
|
|
102
|
+
guidance: [
|
|
103
|
+
"List malformed inputs, error paths, and boundary conditions the tests cover.",
|
|
104
|
+
"Point to the specific test files or cases that assert each negative scenario.",
|
|
105
|
+
"Return verdict 'omitted' only if the task has no meaningful negative surface.",
|
|
106
|
+
].join("\n"),
|
|
107
|
+
promptSection: "Negative Tests",
|
|
108
|
+
},
|
|
109
|
+
Q8: {
|
|
110
|
+
id: "Q8",
|
|
111
|
+
scope: "slice",
|
|
112
|
+
ownerTurn: "complete-slice",
|
|
113
|
+
question: "How will ops know this slice is healthy or broken?",
|
|
114
|
+
guidance: [
|
|
115
|
+
"Describe the health signal (metric, log line, dashboard) that proves the slice works.",
|
|
116
|
+
"Describe the failure signal that triggers an alert or paging.",
|
|
117
|
+
"Document the recovery procedure and any monitoring gaps.",
|
|
118
|
+
"Return verdict 'omitted' only for slices with no runtime behavior at all.",
|
|
119
|
+
].join("\n"),
|
|
120
|
+
promptSection: "Operational Readiness",
|
|
121
|
+
},
|
|
122
|
+
MV01: {
|
|
123
|
+
id: "MV01",
|
|
124
|
+
scope: "milestone",
|
|
125
|
+
ownerTurn: "validate-milestone",
|
|
126
|
+
question: "Is every success criterion in the milestone roadmap satisfied?",
|
|
127
|
+
guidance: [
|
|
128
|
+
"Walk the success-criteria checklist from the milestone roadmap.",
|
|
129
|
+
"For each criterion, point to the slice / assessment / verification evidence that proves it.",
|
|
130
|
+
"Return verdict 'flag' if any criterion is unmet or unverifiable.",
|
|
131
|
+
].join("\n"),
|
|
132
|
+
promptSection: "Success Criteria Checklist",
|
|
133
|
+
},
|
|
134
|
+
MV02: {
|
|
135
|
+
id: "MV02",
|
|
136
|
+
scope: "milestone",
|
|
137
|
+
ownerTurn: "validate-milestone",
|
|
138
|
+
question: "Does every slice have a SUMMARY.md and a passing assessment?",
|
|
139
|
+
guidance: [
|
|
140
|
+
"Confirm every slice listed in the roadmap has a SUMMARY.md.",
|
|
141
|
+
"Confirm each slice has an ASSESSMENT verdict of 'pass' (or justified 'omitted').",
|
|
142
|
+
"Flag missing artifacts and slices with outstanding follow-ups or known limitations.",
|
|
143
|
+
].join("\n"),
|
|
144
|
+
promptSection: "Slice Delivery Audit",
|
|
145
|
+
},
|
|
146
|
+
MV03: {
|
|
147
|
+
id: "MV03",
|
|
148
|
+
scope: "milestone",
|
|
149
|
+
ownerTurn: "validate-milestone",
|
|
150
|
+
question: "Do the slices integrate end-to-end?",
|
|
151
|
+
guidance: [
|
|
152
|
+
"Trace at least one cross-slice flow proving the pieces compose.",
|
|
153
|
+
"Flag gaps where two slices were built in isolation with no integration evidence.",
|
|
154
|
+
].join("\n"),
|
|
155
|
+
promptSection: "Cross-Slice Integration",
|
|
156
|
+
},
|
|
157
|
+
MV04: {
|
|
158
|
+
id: "MV04",
|
|
159
|
+
scope: "milestone",
|
|
160
|
+
ownerTurn: "validate-milestone",
|
|
161
|
+
question: "Are all touched requirements covered and still coherent?",
|
|
162
|
+
guidance: [
|
|
163
|
+
"For each requirement advanced, validated, surfaced, or invalidated across the milestone's slices, confirm the milestone-level evidence matches.",
|
|
164
|
+
"Flag requirements that slices claim to advance but no artifact proves.",
|
|
165
|
+
].join("\n"),
|
|
166
|
+
promptSection: "Requirement Coverage",
|
|
167
|
+
},
|
|
168
|
+
} as const satisfies Record<GateId, GateDefinition>;
|
|
169
|
+
|
|
170
|
+
export type GateRegistry = typeof GATE_REGISTRY;
|
|
171
|
+
|
|
172
|
+
/** Stable ordered lists per owner turn — iteration order matches declaration. */
|
|
173
|
+
const ORDERED_GATES: readonly GateDefinition[] = Object.values(GATE_REGISTRY) as readonly GateDefinition[];
|
|
174
|
+
|
|
175
|
+
/** Return every gate owned by a turn, in stable declaration order. */
|
|
176
|
+
export function getGatesForTurn(turn: OwnerTurn): GateDefinition[] {
|
|
177
|
+
return ORDERED_GATES.filter((g) => g.ownerTurn === turn);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Return the set of gate ids a turn owns. */
|
|
181
|
+
export function getGateIdsForTurn(turn: OwnerTurn): Set<GateId> {
|
|
182
|
+
return new Set(getGatesForTurn(turn).map((g) => g.id));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Look up a definition by gate id, or undefined if unknown. */
|
|
186
|
+
export function getGateDefinition(id: string): GateDefinition | undefined {
|
|
187
|
+
return (GATE_REGISTRY as Record<string, GateDefinition>)[id];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Look up the owner turn for a gate id. Throws if the gate is unknown. */
|
|
191
|
+
export function getOwnerTurn(id: GateId): OwnerTurn {
|
|
192
|
+
const def = GATE_REGISTRY[id];
|
|
193
|
+
if (!def) {
|
|
194
|
+
throw new GSDError(GSD_PARSE_ERROR, `gate-registry: unknown gate id "${id}"`);
|
|
195
|
+
}
|
|
196
|
+
return def.ownerTurn;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Assert that the pending gate rows for a turn match what the registry says
|
|
201
|
+
* the turn owns. Fails loudly rather than silently skipping.
|
|
202
|
+
*
|
|
203
|
+
* - Every row in `pending` must have a definition whose `ownerTurn` matches `turn`.
|
|
204
|
+
* (The caller is responsible for scoping the pending list — e.g. filtering
|
|
205
|
+
* by slice scope before passing it in.)
|
|
206
|
+
* - `options.requireAll` (default true): every gate the turn owns must appear
|
|
207
|
+
* in `pending`. Set to false for turns like `execute-task` that only need
|
|
208
|
+
* coverage for the subset of gates that were seeded (e.g. tasks with no
|
|
209
|
+
* external dependencies have no Q5 row).
|
|
210
|
+
*/
|
|
211
|
+
export function assertGateCoverage(
|
|
212
|
+
pending: ReadonlyArray<Pick<GateRow, "gate_id">>,
|
|
213
|
+
turn: OwnerTurn,
|
|
214
|
+
options: { requireAll?: boolean } = {},
|
|
215
|
+
): void {
|
|
216
|
+
const requireAll = options.requireAll ?? true;
|
|
217
|
+
const expected = getGateIdsForTurn(turn);
|
|
218
|
+
const pendingIds = new Set(pending.map((g) => g.gate_id));
|
|
219
|
+
|
|
220
|
+
const unknown: string[] = [];
|
|
221
|
+
for (const id of pendingIds) {
|
|
222
|
+
const def = getGateDefinition(id);
|
|
223
|
+
if (!def) {
|
|
224
|
+
unknown.push(id);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (def.ownerTurn !== turn) {
|
|
228
|
+
unknown.push(`${id} (owned by ${def.ownerTurn}, not ${turn})`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (unknown.length > 0) {
|
|
233
|
+
throw new GSDError(
|
|
234
|
+
GSD_PARSE_ERROR,
|
|
235
|
+
`assertGateCoverage: turn "${turn}" received pending gates it does not own: ${unknown.join(", ")}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (requireAll) {
|
|
240
|
+
const missing: GateId[] = [];
|
|
241
|
+
for (const id of expected) {
|
|
242
|
+
if (!pendingIds.has(id)) missing.push(id);
|
|
243
|
+
}
|
|
244
|
+
if (missing.length > 0) {
|
|
245
|
+
throw new GSDError(
|
|
246
|
+
GSD_PARSE_ERROR,
|
|
247
|
+
`assertGateCoverage: turn "${turn}" is missing required gates: ${missing.join(", ")}`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -10,6 +10,7 @@ import { existsSync, copyFileSync, mkdirSync, realpathSync } from "node:fs";
|
|
|
10
10
|
import { dirname } from "node:path";
|
|
11
11
|
import type { Decision, Requirement, GateRow, GateId, GateScope, GateStatus, GateVerdict } from "./types.js";
|
|
12
12
|
import { GSDError, GSD_STALE_STATE } from "./errors.js";
|
|
13
|
+
import { getGateIdsForTurn, type OwnerTurn } from "./gate-registry.js";
|
|
13
14
|
import { logError, logWarning } from "./workflow-logger.js";
|
|
14
15
|
|
|
15
16
|
const _require = createRequire(import.meta.url);
|
|
@@ -2302,3 +2303,53 @@ export function getPendingSliceGateCount(milestoneId: string, sliceId: string):
|
|
|
2302
2303
|
).get({ ":mid": milestoneId, ":sid": sliceId });
|
|
2303
2304
|
return row ? (row["cnt"] as number) : 0;
|
|
2304
2305
|
}
|
|
2306
|
+
|
|
2307
|
+
/**
|
|
2308
|
+
* Return pending gate rows owned by a specific workflow turn.
|
|
2309
|
+
*
|
|
2310
|
+
* Unlike `getPendingGates(..., scope)`, this filters by the registry's
|
|
2311
|
+
* `ownerTurn` metadata so callers can distinguish Q3/Q4 (owned by
|
|
2312
|
+
* gate-evaluate) from Q8 (owned by complete-slice) even though both are
|
|
2313
|
+
* scope:"slice". Pass `taskId` to narrow task-scoped results to one task.
|
|
2314
|
+
*/
|
|
2315
|
+
export function getPendingGatesForTurn(
|
|
2316
|
+
milestoneId: string,
|
|
2317
|
+
sliceId: string,
|
|
2318
|
+
turn: OwnerTurn,
|
|
2319
|
+
taskId?: string,
|
|
2320
|
+
): GateRow[] {
|
|
2321
|
+
if (!currentDb) return [];
|
|
2322
|
+
const ids = getGateIdsForTurn(turn);
|
|
2323
|
+
if (ids.size === 0) return [];
|
|
2324
|
+
const idList = [...ids];
|
|
2325
|
+
const placeholders = idList.map((_, i) => `:gid${i}`).join(",");
|
|
2326
|
+
const params: Record<string, unknown> = {
|
|
2327
|
+
":mid": milestoneId,
|
|
2328
|
+
":sid": sliceId,
|
|
2329
|
+
};
|
|
2330
|
+
idList.forEach((id, i) => {
|
|
2331
|
+
params[`:gid${i}`] = id;
|
|
2332
|
+
});
|
|
2333
|
+
let sql =
|
|
2334
|
+
`SELECT * FROM quality_gates
|
|
2335
|
+
WHERE milestone_id = :mid AND slice_id = :sid
|
|
2336
|
+
AND status = 'pending'
|
|
2337
|
+
AND gate_id IN (${placeholders})`;
|
|
2338
|
+
if (taskId !== undefined) {
|
|
2339
|
+
sql += ` AND task_id = :tid`;
|
|
2340
|
+
params[":tid"] = taskId;
|
|
2341
|
+
}
|
|
2342
|
+
return currentDb.prepare(sql).all(params).map(rowToGate);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Count pending gates for a turn. Convenience wrapper used by state
|
|
2347
|
+
* derivation to decide whether a phase transition should pause.
|
|
2348
|
+
*/
|
|
2349
|
+
export function getPendingGateCountForTurn(
|
|
2350
|
+
milestoneId: string,
|
|
2351
|
+
sliceId: string,
|
|
2352
|
+
turn: OwnerTurn,
|
|
2353
|
+
): number {
|
|
2354
|
+
return getPendingGatesForTurn(milestoneId, sliceId, turn).length;
|
|
2355
|
+
}
|
|
@@ -6,19 +6,13 @@
|
|
|
6
6
|
* records in the DB. This module inserts milestone-level validation gates
|
|
7
7
|
* that correspond to the validation checks performed.
|
|
8
8
|
*
|
|
9
|
-
* Gate IDs for milestone validation
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* MV03 — Cross-slice integration
|
|
13
|
-
* MV04 — Requirement coverage
|
|
14
|
-
*
|
|
15
|
-
* These use the existing quality_gates table with scope "milestone".
|
|
9
|
+
* Gate IDs for milestone validation (MV01–MV04) are sourced from the
|
|
10
|
+
* gate registry so the definitions stay in lockstep with prompt builders,
|
|
11
|
+
* dispatch rules, and state derivation. See gate-registry.ts.
|
|
16
12
|
*/
|
|
17
13
|
|
|
18
14
|
import { _getAdapter } from "./gsd-db.js";
|
|
19
|
-
|
|
20
|
-
/** Milestone validation gate IDs. */
|
|
21
|
-
const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"] as const;
|
|
15
|
+
import { getGatesForTurn } from "./gate-registry.js";
|
|
22
16
|
|
|
23
17
|
/**
|
|
24
18
|
* Insert milestone-level quality_gates records for a validation run.
|
|
@@ -27,6 +21,9 @@ const MILESTONE_GATE_IDS = ["MV01", "MV02", "MV03", "MV04"] as const;
|
|
|
27
21
|
* from the overall milestone validation verdict. Individual gate-level
|
|
28
22
|
* verdicts are not available (the handler receives a single verdict),
|
|
29
23
|
* so all gates share the overall verdict.
|
|
24
|
+
*
|
|
25
|
+
* Gate IDs come from the registry — adding/removing an MV-scoped gate
|
|
26
|
+
* in gate-registry.ts automatically flows through here.
|
|
30
27
|
*/
|
|
31
28
|
export function insertMilestoneValidationGates(
|
|
32
29
|
milestoneId: string,
|
|
@@ -38,8 +35,9 @@ export function insertMilestoneValidationGates(
|
|
|
38
35
|
if (!db) return;
|
|
39
36
|
|
|
40
37
|
const gateVerdict = verdict === "pass" ? "pass" : "flag";
|
|
38
|
+
const milestoneGates = getGatesForTurn("validate-milestone");
|
|
41
39
|
|
|
42
|
-
for (const
|
|
40
|
+
for (const def of milestoneGates) {
|
|
43
41
|
db.prepare(
|
|
44
42
|
`INSERT OR REPLACE INTO quality_gates
|
|
45
43
|
(milestone_id, slice_id, gate_id, scope, task_id, status, verdict, rationale, findings, evaluated_at)
|
|
@@ -47,9 +45,9 @@ export function insertMilestoneValidationGates(
|
|
|
47
45
|
).run({
|
|
48
46
|
":mid": milestoneId,
|
|
49
47
|
":sid": sliceId,
|
|
50
|
-
":gid":
|
|
48
|
+
":gid": def.id,
|
|
51
49
|
":verdict": gateVerdict,
|
|
52
|
-
":rationale":
|
|
50
|
+
":rationale": `${def.promptSection} — milestone validation verdict: ${verdict}`,
|
|
53
51
|
":evaluated_at": evaluatedAt,
|
|
54
52
|
});
|
|
55
53
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
readNotifications,
|
|
10
10
|
markAllRead,
|
|
11
11
|
clearNotifications,
|
|
12
|
+
onNotificationStoreChange,
|
|
12
13
|
type NotificationEntry,
|
|
13
14
|
type NotifySeverity,
|
|
14
15
|
} from "./notification-store.js";
|
|
@@ -82,6 +83,7 @@ export class GSDNotificationOverlay {
|
|
|
82
83
|
private refreshTimer: ReturnType<typeof setInterval>;
|
|
83
84
|
private disposed = false;
|
|
84
85
|
private resizeHandler: (() => void) | null = null;
|
|
86
|
+
private unsubscribeStore: (() => void) | null = null;
|
|
85
87
|
|
|
86
88
|
constructor(
|
|
87
89
|
tui: { requestRender: () => void },
|
|
@@ -105,19 +107,17 @@ export class GSDNotificationOverlay {
|
|
|
105
107
|
};
|
|
106
108
|
process.stdout.on("resize", this.resizeHandler);
|
|
107
109
|
|
|
108
|
-
//
|
|
110
|
+
// Subscribe to store mutations for immediate updates
|
|
111
|
+
this.unsubscribeStore = onNotificationStoreChange(() => {
|
|
112
|
+
if (this.disposed) return;
|
|
113
|
+
this._refreshFromDisk();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 30s safety-net for cross-process edits (web subprocess, parallel workers)
|
|
109
117
|
this.refreshTimer = setInterval(() => {
|
|
110
118
|
if (this.disposed) return;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (signature !== this.entriesSignature) {
|
|
114
|
-
markAllRead();
|
|
115
|
-
this.entries = readNotifications();
|
|
116
|
-
this.entriesSignature = notificationSignature(this.entries);
|
|
117
|
-
this.invalidate();
|
|
118
|
-
this.tui.requestRender();
|
|
119
|
-
}
|
|
120
|
-
}, 3000);
|
|
119
|
+
this._refreshFromDisk();
|
|
120
|
+
}, 30_000);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
private get filter(): FilterMode {
|
|
@@ -215,12 +215,28 @@ export class GSDNotificationOverlay {
|
|
|
215
215
|
dispose(): void {
|
|
216
216
|
this.disposed = true;
|
|
217
217
|
clearInterval(this.refreshTimer);
|
|
218
|
+
if (this.unsubscribeStore) {
|
|
219
|
+
this.unsubscribeStore();
|
|
220
|
+
this.unsubscribeStore = null;
|
|
221
|
+
}
|
|
218
222
|
if (this.resizeHandler) {
|
|
219
223
|
process.stdout.removeListener("resize", this.resizeHandler);
|
|
220
224
|
this.resizeHandler = null;
|
|
221
225
|
}
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
private _refreshFromDisk(): void {
|
|
229
|
+
const fresh = readNotifications();
|
|
230
|
+
const signature = notificationSignature(fresh);
|
|
231
|
+
if (signature !== this.entriesSignature) {
|
|
232
|
+
markAllRead();
|
|
233
|
+
this.entries = readNotifications();
|
|
234
|
+
this.entriesSignature = notificationSignature(this.entries);
|
|
235
|
+
this.invalidate();
|
|
236
|
+
this.tui.requestRender();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
private wrapInBox(inner: string[], width: number): string[] {
|
|
225
241
|
const th = this.theme;
|
|
226
242
|
const border = (s: string) => th.fg("borderAccent", s);
|
|
@@ -323,10 +323,11 @@ function _withLock<T>(basePath: string, fn: () => T): T {
|
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
//
|
|
327
|
-
|
|
326
|
+
// Best-effort: mutation runs regardless of lock status (idempotent overwrites).
|
|
327
|
+
// createdLock gates cleanup only — never skip fn() on lock failure.
|
|
328
|
+
const createdLock = fd !== null;
|
|
328
329
|
try {
|
|
329
|
-
if (
|
|
330
|
+
if (createdLock && fd !== null) {
|
|
330
331
|
// Write our PID timestamp into the lock for stale detection
|
|
331
332
|
writeFileSync(lockPath, String(Date.now()), "utf-8");
|
|
332
333
|
closeSync(fd);
|
|
@@ -334,7 +335,7 @@ function _withLock<T>(basePath: string, fn: () => T): T {
|
|
|
334
335
|
return fn();
|
|
335
336
|
} finally {
|
|
336
337
|
// Only delete the lock if we created it — never remove another process's lock
|
|
337
|
-
if (
|
|
338
|
+
if (createdLock) {
|
|
338
339
|
try { unlinkSync(lockPath); } catch { /* best-effort cleanup */ }
|
|
339
340
|
}
|
|
340
341
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD Prompt Validation — Validates enhanced context and turn output
|
|
3
|
+
* artifacts before writing.
|
|
4
|
+
*
|
|
5
|
+
* Implements R109 validation requirement: CONTEXT.md must have required
|
|
6
|
+
* sections before being written to disk. Additionally, per-turn validators
|
|
7
|
+
* check that artifacts produced by gate-owning turns contain the gate
|
|
8
|
+
* sections declared in gate-registry.ts, so a malformed summary/validation
|
|
9
|
+
* markdown file cannot silently drop a quality gate.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { getGatesForTurn, type OwnerTurn } from "./gate-registry.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Result of validating enhanced context output.
|
|
16
|
+
*/
|
|
17
|
+
export interface ValidationResult {
|
|
18
|
+
/** Whether all required sections are present. */
|
|
19
|
+
valid: boolean;
|
|
20
|
+
/** List of missing required sections. */
|
|
21
|
+
missing: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate that enhanced context content has all required sections.
|
|
26
|
+
*
|
|
27
|
+
* Required sections per R109:
|
|
28
|
+
* - Scope section (## Scope, ## Milestone Scope, or ## Why This Milestone)
|
|
29
|
+
* - Architectural Decisions section (## Architectural Decisions)
|
|
30
|
+
* - Acceptance Criteria section (## Acceptance Criteria or ## Final Integrated Acceptance)
|
|
31
|
+
*
|
|
32
|
+
* Additionally validates that the Architectural Decisions section contains
|
|
33
|
+
* at least one decision entry (### heading or **Decision marker).
|
|
34
|
+
*
|
|
35
|
+
* @param content - The enhanced context markdown content
|
|
36
|
+
* @returns ValidationResult with valid flag and list of missing sections
|
|
37
|
+
*/
|
|
38
|
+
export function validateEnhancedContext(content: string): ValidationResult {
|
|
39
|
+
const missing: string[] = [];
|
|
40
|
+
|
|
41
|
+
// Required section 1: Scope (multiple acceptable header variants)
|
|
42
|
+
const hasScopeSection =
|
|
43
|
+
/^## Scope\b/m.test(content) ||
|
|
44
|
+
/^## Milestone Scope\b/m.test(content) ||
|
|
45
|
+
/^## Why This Milestone\b/m.test(content);
|
|
46
|
+
|
|
47
|
+
if (!hasScopeSection) {
|
|
48
|
+
missing.push("Milestone Scope or Why This Milestone");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Required section 2: Architectural Decisions
|
|
52
|
+
const hasArchitecturalDecisions = /^## Architectural Decisions\b/m.test(content);
|
|
53
|
+
if (!hasArchitecturalDecisions) {
|
|
54
|
+
missing.push("Architectural Decisions");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Required section 3: Acceptance Criteria (multiple acceptable header variants)
|
|
58
|
+
const hasAcceptanceCriteria =
|
|
59
|
+
/^## Acceptance Criteria\b/m.test(content) ||
|
|
60
|
+
/^## Final Integrated Acceptance\b/m.test(content);
|
|
61
|
+
|
|
62
|
+
if (!hasAcceptanceCriteria) {
|
|
63
|
+
missing.push("Acceptance Criteria");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Additional validation: Architectural Decisions must have at least one entry
|
|
67
|
+
if (hasArchitecturalDecisions) {
|
|
68
|
+
// Extract the section content between ## Architectural Decisions and the next ## heading.
|
|
69
|
+
// Uses indexOf-based extraction instead of regex with \z (which is invalid in JavaScript
|
|
70
|
+
// regex — it's PCRE/Ruby syntax and JS treats it as literal 'z').
|
|
71
|
+
const sectionStart = content.indexOf("## Architectural Decisions");
|
|
72
|
+
if (sectionStart === -1) {
|
|
73
|
+
missing.push("Architectural Decisions");
|
|
74
|
+
} else {
|
|
75
|
+
const afterHeading = content.slice(sectionStart + "## Architectural Decisions".length);
|
|
76
|
+
const nextSection = afterHeading.search(/^## /m);
|
|
77
|
+
const sectionContent = nextSection === -1 ? afterHeading : afterHeading.slice(0, nextSection);
|
|
78
|
+
|
|
79
|
+
// Check for actual decision entries:
|
|
80
|
+
// - ### heading (subsection per decision)
|
|
81
|
+
// - **Decision marker (inline decision format)
|
|
82
|
+
const hasDecisionEntry = /^### /m.test(sectionContent) || /^\*\*Decision/m.test(sectionContent);
|
|
83
|
+
|
|
84
|
+
if (!hasDecisionEntry) {
|
|
85
|
+
missing.push("At least one architectural decision entry");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
valid: missing.length === 0,
|
|
92
|
+
missing,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Per-Turn Gate Section Validators ─────────────────────────────────────
|
|
97
|
+
//
|
|
98
|
+
// Each validator checks that the artifact written by a turn contains a
|
|
99
|
+
// heading for every gate owned by that turn. The registry is the source
|
|
100
|
+
// of truth for which sections must exist; adding a new gate automatically
|
|
101
|
+
// flows through via `getGatesForTurn(turn)`.
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Escape a string so it can be embedded safely inside a regular expression.
|
|
105
|
+
*/
|
|
106
|
+
function escapeRegExp(value: string): string {
|
|
107
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Validate that an artifact contains an `## H2` heading for every gate the
|
|
112
|
+
* named turn owns. Returns the list of missing gate section headers.
|
|
113
|
+
*
|
|
114
|
+
* Soft rule: a section counts as "present" if it is declared (H2 heading
|
|
115
|
+
* exists) — empty-body sections are allowed and handled by the tool
|
|
116
|
+
* handler, which will record such gates as `omitted`.
|
|
117
|
+
*/
|
|
118
|
+
export function validateGateSections(
|
|
119
|
+
content: string,
|
|
120
|
+
turn: OwnerTurn,
|
|
121
|
+
): ValidationResult {
|
|
122
|
+
const missing: string[] = [];
|
|
123
|
+
for (const def of getGatesForTurn(turn)) {
|
|
124
|
+
const pattern = new RegExp(`^##\\s+${escapeRegExp(def.promptSection)}\\b`, "m");
|
|
125
|
+
if (!pattern.test(content)) {
|
|
126
|
+
missing.push(`${def.id} (## ${def.promptSection})`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { valid: missing.length === 0, missing };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Validate a SUMMARY.md produced by the complete-slice turn. Requires
|
|
134
|
+
* an H2 heading for every gate owned by complete-slice (e.g. Q8 →
|
|
135
|
+
* "## Operational Readiness"). Intended for use in the tool handler's
|
|
136
|
+
* pre-write checks or in the post-unit validation sweep.
|
|
137
|
+
*/
|
|
138
|
+
export function validateSliceSummaryOutput(content: string): ValidationResult {
|
|
139
|
+
return validateGateSections(content, "complete-slice");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate a task SUMMARY.md produced by the execute-task turn. Only
|
|
144
|
+
* flags gates that are still pending for the task; skips the check
|
|
145
|
+
* when no rows are seeded (simple task).
|
|
146
|
+
*/
|
|
147
|
+
export function validateTaskSummaryOutput(content: string): ValidationResult {
|
|
148
|
+
return validateGateSections(content, "execute-task");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Validate a VALIDATION.md produced by the validate-milestone turn.
|
|
153
|
+
* Requires an H2 heading for every MV gate declared in the registry.
|
|
154
|
+
*/
|
|
155
|
+
export function validateMilestoneValidationOutput(content: string): ValidationResult {
|
|
156
|
+
return validateGateSections(content, "validate-milestone");
|
|
157
|
+
}
|
|
@@ -16,6 +16,8 @@ All relevant context has been preloaded below — the slice plan, all task summa
|
|
|
16
16
|
|
|
17
17
|
{{inlinedContext}}
|
|
18
18
|
|
|
19
|
+
{{gatesToClose}}
|
|
20
|
+
|
|
19
21
|
**Match effort to complexity.** A simple slice with 1-2 tasks needs a brief summary and lightweight verification. A complex slice with 5 tasks across multiple subsystems needs thorough verification and a detailed summary. Scale the work below accordingly.
|
|
20
22
|
|
|
21
23
|
Then:
|
|
@@ -23,7 +25,7 @@ Then:
|
|
|
23
25
|
2. {{skillActivation}}
|
|
24
26
|
3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first. Task artifacts use a **flat file layout** directly inside `tasks/` (for example `T01-SUMMARY.md`, `T02-SUMMARY.md`) rather than per-task subdirectories. If you need to count or re-read task summaries during verification, use `find .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks -name "*-SUMMARY.md"` or `ls .gsd/milestones/{{milestoneId}}/slices/{{sliceId}}/tasks/*-SUMMARY.md`. Never use `tasks/*/SUMMARY.md` — that glob expects subdirectories that do not exist.
|
|
25
27
|
4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
|
|
26
|
-
5.
|
|
28
|
+
5. Address every gate listed in the **Gates to Close** section above — each gate maps to a specific slice-summary section the handler inspects (for example, Q8 maps to **Operational Readiness**: health signal, failure signal, recovery procedure, and monitoring gaps). Leaving a section empty records the gate as `omitted`.
|
|
27
29
|
6. If this slice produced evidence that a requirement changed status (Active → Validated, Active → Deferred, etc.), call `gsd_requirement_update` with the requirement ID, updated `status`, and `validation` evidence. Do NOT write `.gsd/REQUIREMENTS.md` directly — the engine renders it from the database.
|
|
28
30
|
7. Prepare the slice completion content you will pass to `gsd_complete_slice` using the camelCase fields `milestoneId`, `sliceId`, `sliceTitle`, `oneLiner`, `narrative`, `verification`, and `uatContent`. Do **not** manually write `{{sliceSummaryPath}}`. Do **not** manually write `{{sliceUatPath}}` — the DB-backed tool is the canonical write path for both artifacts.
|
|
29
31
|
8. Draft the UAT content you will pass as `uatContent` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
|