forgecraft-mcp 1.3.2 → 1.6.1
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 +2 -2
- package/dist/analyzers/anchors/anchor-loader.d.ts +47 -0
- package/dist/analyzers/anchors/anchor-loader.d.ts.map +1 -0
- package/dist/analyzers/anchors/anchor-loader.js +113 -0
- package/dist/analyzers/anchors/anchor-loader.js.map +1 -0
- package/dist/analyzers/anti-pattern.d.ts.map +1 -1
- package/dist/analyzers/anti-pattern.js +38 -26
- package/dist/analyzers/anti-pattern.js.map +1 -1
- package/dist/analyzers/completeness-helpers.d.ts +5 -0
- package/dist/analyzers/completeness-helpers.d.ts.map +1 -1
- package/dist/analyzers/completeness-helpers.js +17 -0
- package/dist/analyzers/completeness-helpers.js.map +1 -1
- package/dist/analyzers/completeness.d.ts.map +1 -1
- package/dist/analyzers/completeness.js +4 -4
- package/dist/analyzers/completeness.js.map +1 -1
- package/dist/analyzers/gs-scorer.d.ts +3 -1
- package/dist/analyzers/gs-scorer.d.ts.map +1 -1
- package/dist/analyzers/gs-scorer.js +5 -2
- package/dist/analyzers/gs-scorer.js.map +1 -1
- package/dist/analyzers/scorers/composable-scorer.d.ts +4 -2
- package/dist/analyzers/scorers/composable-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/composable-scorer.js +50 -2
- package/dist/analyzers/scorers/composable-scorer.js.map +1 -1
- package/dist/analyzers/scorers/executable-scorer.d.ts +3 -2
- package/dist/analyzers/scorers/executable-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/executable-scorer.js +64 -4
- package/dist/analyzers/scorers/executable-scorer.js.map +1 -1
- package/dist/analyzers/scorers/scorer-utils.d.ts +5 -3
- package/dist/analyzers/scorers/scorer-utils.d.ts.map +1 -1
- package/dist/analyzers/scorers/scorer-utils.js +34 -9
- package/dist/analyzers/scorers/scorer-utils.js.map +1 -1
- package/dist/analyzers/scorers/self-describing-scorer.d.ts +7 -4
- package/dist/analyzers/scorers/self-describing-scorer.d.ts.map +1 -1
- package/dist/analyzers/scorers/self-describing-scorer.js +17 -18
- package/dist/analyzers/scorers/self-describing-scorer.js.map +1 -1
- package/dist/artifacts/commit-hooks.d.ts +1 -1
- package/dist/artifacts/commit-hooks.d.ts.map +1 -1
- package/dist/artifacts/commit-hooks.js +2 -0
- package/dist/artifacts/commit-hooks.js.map +1 -1
- package/dist/cli/commands.d.ts +35 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +109 -2
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +7 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -1
- package/dist/cli.js.map +1 -1
- package/dist/disciplines/catalog.d.ts +16 -0
- package/dist/disciplines/catalog.d.ts.map +1 -0
- package/dist/disciplines/catalog.js +196 -0
- package/dist/disciplines/catalog.js.map +1 -0
- package/dist/disciplines/runner.d.ts +13 -0
- package/dist/disciplines/runner.d.ts.map +1 -0
- package/dist/disciplines/runner.js +35 -0
- package/dist/disciplines/runner.js.map +1 -0
- package/dist/registry/remote-gates.js +1 -1
- package/dist/registry/remote-gates.js.map +1 -1
- package/dist/sentinel/detect.d.ts +41 -0
- package/dist/sentinel/detect.d.ts.map +1 -0
- package/dist/sentinel/detect.js +122 -0
- package/dist/sentinel/detect.js.map +1 -0
- package/dist/sentinel/write.d.ts +54 -0
- package/dist/sentinel/write.d.ts.map +1 -0
- package/dist/sentinel/write.js +75 -0
- package/dist/sentinel/write.js.map +1 -0
- package/dist/shared/cnt-health.d.ts.map +1 -1
- package/dist/shared/cnt-health.js +12 -4
- package/dist/shared/cnt-health.js.map +1 -1
- package/dist/shared/config.d.ts +8 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +23 -0
- package/dist/shared/config.js.map +1 -1
- package/dist/shared/project-gates-helpers.d.ts +9 -0
- package/dist/shared/project-gates-helpers.d.ts.map +1 -1
- package/dist/shared/project-gates-helpers.js +35 -0
- package/dist/shared/project-gates-helpers.js.map +1 -1
- package/dist/shared/result-utils.d.ts +27 -0
- package/dist/shared/result-utils.d.ts.map +1 -0
- package/dist/shared/result-utils.js +41 -0
- package/dist/shared/result-utils.js.map +1 -0
- package/dist/shared/types/config.d.ts +7 -1
- package/dist/shared/types/config.d.ts.map +1 -1
- package/dist/shared/types/gates.d.ts +28 -0
- package/dist/shared/types/gates.d.ts.map +1 -1
- package/dist/shared/types/project.d.ts +66 -0
- package/dist/shared/types/project.d.ts.map +1 -1
- package/dist/shared/types/project.js.map +1 -1
- package/dist/shared/types/verify.d.ts +51 -1
- package/dist/shared/types/verify.d.ts.map +1 -1
- package/dist/shared/types/verify.js +37 -1
- package/dist/shared/types/verify.js.map +1 -1
- package/dist/tools/advise-session-advisor.d.ts +16 -0
- package/dist/tools/advise-session-advisor.d.ts.map +1 -0
- package/dist/tools/advise-session-advisor.js +89 -0
- package/dist/tools/advise-session-advisor.js.map +1 -0
- package/dist/tools/advise-session-signals.d.ts +21 -0
- package/dist/tools/advise-session-signals.d.ts.map +1 -0
- package/dist/tools/advise-session-signals.js +113 -0
- package/dist/tools/advise-session-signals.js.map +1 -0
- package/dist/tools/advise-session.d.ts +22 -0
- package/dist/tools/advise-session.d.ts.map +1 -0
- package/dist/tools/advise-session.js +31 -0
- package/dist/tools/advise-session.js.map +1 -0
- package/dist/tools/change-request.d.ts +53 -0
- package/dist/tools/change-request.d.ts.map +1 -0
- package/dist/tools/change-request.js +375 -0
- package/dist/tools/change-request.js.map +1 -0
- package/dist/tools/check-cascade-contracts.d.ts +13 -0
- package/dist/tools/check-cascade-contracts.d.ts.map +1 -1
- package/dist/tools/check-cascade-contracts.js +73 -2
- package/dist/tools/check-cascade-contracts.js.map +1 -1
- package/dist/tools/check-cascade.d.ts +4 -3
- package/dist/tools/check-cascade.d.ts.map +1 -1
- package/dist/tools/check-cascade.js +30 -12
- package/dist/tools/check-cascade.js.map +1 -1
- package/dist/tools/check-spec-consistency.d.ts +25 -0
- package/dist/tools/check-spec-consistency.d.ts.map +1 -0
- package/dist/tools/check-spec-consistency.js +339 -0
- package/dist/tools/check-spec-consistency.js.map +1 -0
- package/dist/tools/check-t4.d.ts +54 -0
- package/dist/tools/check-t4.d.ts.map +1 -0
- package/dist/tools/check-t4.js +305 -0
- package/dist/tools/check-t4.js.map +1 -0
- package/dist/tools/close-cycle-helpers.d.ts +21 -2
- package/dist/tools/close-cycle-helpers.d.ts.map +1 -1
- package/dist/tools/close-cycle-helpers.js +66 -10
- package/dist/tools/close-cycle-helpers.js.map +1 -1
- package/dist/tools/close-cycle.d.ts +2 -2
- package/dist/tools/close-cycle.d.ts.map +1 -1
- package/dist/tools/close-cycle.js +342 -4
- package/dist/tools/close-cycle.js.map +1 -1
- package/dist/tools/consolidate-status.d.ts +112 -0
- package/dist/tools/consolidate-status.d.ts.map +1 -0
- package/dist/tools/consolidate-status.js +356 -0
- package/dist/tools/consolidate-status.js.map +1 -0
- package/dist/tools/executable-gates.d.ts +52 -0
- package/dist/tools/executable-gates.d.ts.map +1 -0
- package/dist/tools/executable-gates.js +332 -0
- package/dist/tools/executable-gates.js.map +1 -0
- package/dist/tools/forgecraft-dispatch-extended.d.ts.map +1 -1
- package/dist/tools/forgecraft-dispatch-extended.js +75 -0
- package/dist/tools/forgecraft-dispatch-extended.js.map +1 -1
- package/dist/tools/forgecraft-dispatch.d.ts.map +1 -1
- package/dist/tools/forgecraft-dispatch.js +21 -0
- package/dist/tools/forgecraft-dispatch.js.map +1 -1
- package/dist/tools/forgecraft-router.d.ts +8 -0
- package/dist/tools/forgecraft-router.d.ts.map +1 -1
- package/dist/tools/forgecraft-router.js +21 -1
- package/dist/tools/forgecraft-router.js.map +1 -1
- package/dist/tools/forgecraft-schema-params.d.ts +61 -4
- package/dist/tools/forgecraft-schema-params.d.ts.map +1 -1
- package/dist/tools/forgecraft-schema-params.js +95 -0
- package/dist/tools/forgecraft-schema-params.js.map +1 -1
- package/dist/tools/forgecraft-schema.d.ts +62 -5
- package/dist/tools/forgecraft-schema.d.ts.map +1 -1
- package/dist/tools/forgecraft-schema.js +24 -0
- package/dist/tools/forgecraft-schema.js.map +1 -1
- package/dist/tools/gate-violations.d.ts +59 -0
- package/dist/tools/gate-violations.d.ts.map +1 -0
- package/dist/tools/gate-violations.js +152 -0
- package/dist/tools/gate-violations.js.map +1 -0
- package/dist/tools/generate-adr.js +6 -6
- package/dist/tools/generate-adr.js.map +1 -1
- package/dist/tools/generate-env-probe.d.ts +49 -0
- package/dist/tools/generate-env-probe.d.ts.map +1 -0
- package/dist/tools/generate-env-probe.js +365 -0
- package/dist/tools/generate-env-probe.js.map +1 -0
- package/dist/tools/generate-harness.d.ts +52 -0
- package/dist/tools/generate-harness.d.ts.map +1 -0
- package/dist/tools/generate-harness.js +333 -0
- package/dist/tools/generate-harness.js.map +1 -0
- package/dist/tools/generate-roadmap.d.ts +1 -1
- package/dist/tools/generate-roadmap.d.ts.map +1 -1
- package/dist/tools/generate-roadmap.js +38 -4
- package/dist/tools/generate-roadmap.js.map +1 -1
- package/dist/tools/generate-session-prompt.d.ts +4 -4
- package/dist/tools/generate-session-prompt.d.ts.map +1 -1
- package/dist/tools/generate-session-prompt.js +66 -16
- package/dist/tools/generate-session-prompt.js.map +1 -1
- package/dist/tools/generate-slo-probe.d.ts +53 -0
- package/dist/tools/generate-slo-probe.d.ts.map +1 -0
- package/dist/tools/generate-slo-probe.js +366 -0
- package/dist/tools/generate-slo-probe.js.map +1 -0
- package/dist/tools/layer-status-gates.d.ts +24 -0
- package/dist/tools/layer-status-gates.d.ts.map +1 -0
- package/dist/tools/layer-status-gates.js +151 -0
- package/dist/tools/layer-status-gates.js.map +1 -0
- package/dist/tools/layer-status.d.ts +133 -0
- package/dist/tools/layer-status.d.ts.map +1 -0
- package/dist/tools/layer-status.js +593 -0
- package/dist/tools/layer-status.js.map +1 -0
- package/dist/tools/postcondition-coverage.d.ts +57 -0
- package/dist/tools/postcondition-coverage.d.ts.map +1 -0
- package/dist/tools/postcondition-coverage.js +256 -0
- package/dist/tools/postcondition-coverage.js.map +1 -0
- package/dist/tools/probe-runners.d.ts +21 -0
- package/dist/tools/probe-runners.d.ts.map +1 -0
- package/dist/tools/probe-runners.js +246 -0
- package/dist/tools/probe-runners.js.map +1 -0
- package/dist/tools/probe-templates.d.ts +27 -0
- package/dist/tools/probe-templates.d.ts.map +1 -0
- package/dist/tools/probe-templates.js +279 -0
- package/dist/tools/probe-templates.js.map +1 -0
- package/dist/tools/propose-session.d.ts +28 -0
- package/dist/tools/propose-session.d.ts.map +1 -0
- package/dist/tools/propose-session.js +333 -0
- package/dist/tools/propose-session.js.map +1 -0
- package/dist/tools/roadmap-builder.d.ts +34 -1
- package/dist/tools/roadmap-builder.d.ts.map +1 -1
- package/dist/tools/roadmap-builder.js +153 -11
- package/dist/tools/roadmap-builder.js.map +1 -1
- package/dist/tools/run-env-probe.d.ts +57 -0
- package/dist/tools/run-env-probe.d.ts.map +1 -0
- package/dist/tools/run-env-probe.js +270 -0
- package/dist/tools/run-env-probe.js.map +1 -0
- package/dist/tools/run-harness.d.ts +52 -0
- package/dist/tools/run-harness.d.ts.map +1 -0
- package/dist/tools/run-harness.js +279 -0
- package/dist/tools/run-harness.js.map +1 -0
- package/dist/tools/run-slo-probe.d.ts +50 -0
- package/dist/tools/run-slo-probe.d.ts.map +1 -0
- package/dist/tools/run-slo-probe.js +281 -0
- package/dist/tools/run-slo-probe.js.map +1 -0
- package/dist/tools/scaffold-writer.d.ts.map +1 -1
- package/dist/tools/scaffold-writer.js +4 -0
- package/dist/tools/scaffold-writer.js.map +1 -1
- package/dist/tools/session-prompt-builders.d.ts +20 -0
- package/dist/tools/session-prompt-builders.d.ts.map +1 -1
- package/dist/tools/session-prompt-builders.js +111 -14
- package/dist/tools/session-prompt-builders.js.map +1 -1
- package/dist/tools/session-prompt-sections.d.ts +4 -2
- package/dist/tools/session-prompt-sections.d.ts.map +1 -1
- package/dist/tools/session-prompt-sections.js +22 -10
- package/dist/tools/session-prompt-sections.js.map +1 -1
- package/dist/tools/setup-monitoring.d.ts +41 -0
- package/dist/tools/setup-monitoring.d.ts.map +1 -0
- package/dist/tools/setup-monitoring.js +364 -0
- package/dist/tools/setup-monitoring.js.map +1 -0
- package/dist/tools/verify-formatter.d.ts.map +1 -1
- package/dist/tools/verify-formatter.js +15 -1
- package/dist/tools/verify-formatter.js.map +1 -1
- package/dist/tools/verify.d.ts.map +1 -1
- package/dist/tools/verify.js +3 -0
- package/dist/tools/verify.js.map +1 -1
- package/package.json +11 -2
- package/templates/api/harness/uc-template.hurl +20 -0
- package/templates/docs-manifest.yaml +224 -0
- package/templates/game/harness/uc-template.sim.ts +29 -0
- package/templates/universal/claude-md-blocks/layer-navigation.md +20 -0
- package/templates/universal/claude-md-blocks/nfr-contracts.md +22 -0
- package/templates/universal/hooks.yaml +212 -20
- package/templates/web-react/harness/uc-template.spec.ts +35 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Probe content template generators for generate_harness.
|
|
3
|
+
* Each function returns the file content for a given probe type.
|
|
4
|
+
*/
|
|
5
|
+
// ── Template generators ───────────────────────────────────────────────
|
|
6
|
+
export function generatePlaywrightProbe(ucId, title, precondition, postcondition, steps, scenario = "happy") {
|
|
7
|
+
const stepComments = steps.length > 0
|
|
8
|
+
? steps.map((s, i) => ` // Step ${i + 1}: ${s}`).join("\n")
|
|
9
|
+
: ` // TODO: implement ${ucId} main flow steps`;
|
|
10
|
+
return `import { test, expect } from '@playwright/test';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
14
|
+
* Precondition: ${precondition}
|
|
15
|
+
* Postcondition: ${postcondition}
|
|
16
|
+
*/
|
|
17
|
+
test.describe('${ucId}: ${title}', () => {
|
|
18
|
+
test('postcondition: ${postcondition}', async ({ page }) => {
|
|
19
|
+
${stepComments}
|
|
20
|
+
throw new Error('Probe not yet implemented — fill in the ${ucId} flow');
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
export function generateHurlProbe(ucId, title, precondition, postcondition, steps, scenario = "happy") {
|
|
26
|
+
const stepComments = steps.length > 0
|
|
27
|
+
? steps.map((s, i) => `# Step ${i + 1}: ${s}`).join("\n")
|
|
28
|
+
: `# TODO: implement ${ucId} main flow steps`;
|
|
29
|
+
return `# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
30
|
+
# Precondition: ${precondition}
|
|
31
|
+
# Postcondition: ${postcondition}
|
|
32
|
+
${stepComments}
|
|
33
|
+
POST http://{{host}}/api/endpoint
|
|
34
|
+
Content-Type: application/json
|
|
35
|
+
{ "field": "value" }
|
|
36
|
+
HTTP 200
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
export function generateGraphqlHurlProbe(ucId, title, postcondition, scenario = "happy") {
|
|
40
|
+
return `# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
41
|
+
# GraphQL postcondition: ${postcondition}
|
|
42
|
+
POST http://{{host}}/graphql
|
|
43
|
+
Content-Type: application/json
|
|
44
|
+
{ "query": "query { __typename }", "variables": {} }
|
|
45
|
+
HTTP 200
|
|
46
|
+
[Asserts]
|
|
47
|
+
jsonpath "$.data" exists
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
export function generateShProbe(ucId, title, postcondition, probeType, scenario = "happy") {
|
|
51
|
+
if (probeType === "mcp_call") {
|
|
52
|
+
return `#!/usr/bin/env bash
|
|
53
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
54
|
+
# Verifies MCP action postconditions
|
|
55
|
+
set -euo pipefail
|
|
56
|
+
echo "TODO: implement ${ucId} MCP probe"
|
|
57
|
+
exit 1
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
return `#!/usr/bin/env bash
|
|
61
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
62
|
+
# Postcondition: ${postcondition}
|
|
63
|
+
set -euo pipefail
|
|
64
|
+
echo "PASS: ${ucId} postcondition verified"
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
export function generateDbShProbe(ucId, title, postcondition, scenario = "happy") {
|
|
68
|
+
return `#!/usr/bin/env bash
|
|
69
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
70
|
+
# DB postcondition: ${postcondition}
|
|
71
|
+
set -euo pipefail
|
|
72
|
+
DB_URL="\${DATABASE_URL:-}"
|
|
73
|
+
if [ -z "$DB_URL" ]; then
|
|
74
|
+
echo "SKIP: DATABASE_URL not set — cannot verify DB postcondition"
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
echo "TODO: implement DB postcondition check for ${ucId} [${scenario}]"
|
|
78
|
+
exit 1
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
export function generateMqShProbe(ucId, title, postcondition, scenario = "happy") {
|
|
82
|
+
return `#!/usr/bin/env bash
|
|
83
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
84
|
+
# Queue postcondition: ${postcondition}
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
BROKER="\${KAFKA_BROKER:?KAFKA_BROKER is required}"
|
|
87
|
+
TOPIC="\${QUEUE_TOPIC:-}"
|
|
88
|
+
if command -v kcat &>/dev/null; then
|
|
89
|
+
echo "TODO: assert event published to $TOPIC via kcat"
|
|
90
|
+
exit 1
|
|
91
|
+
elif command -v rabbitmqadmin &>/dev/null; then
|
|
92
|
+
echo "TODO: rabbitmqadmin get queue={{queue_name}}"
|
|
93
|
+
exit 1
|
|
94
|
+
else
|
|
95
|
+
echo "SKIP: no queue inspection tool found (kcat, rabbitmqadmin)"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
`;
|
|
99
|
+
}
|
|
100
|
+
export function generateK6Probe(ucId, title, postcondition, scenario = "happy") {
|
|
101
|
+
return `// L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
102
|
+
// NFR contract: ${postcondition}
|
|
103
|
+
import http from 'k6/http';
|
|
104
|
+
import { check, sleep } from 'k6';
|
|
105
|
+
export const options = {
|
|
106
|
+
vus: 10,
|
|
107
|
+
duration: '30s',
|
|
108
|
+
thresholds: {
|
|
109
|
+
http_req_duration: ['p(99)<500'],
|
|
110
|
+
http_req_failed: ['rate<0.01'],
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
export default function () {
|
|
114
|
+
const res = http.get(__ENV.API_URL + '/api/health');
|
|
115
|
+
check(res, { 'status is 200': (r) => r.status === 200 });
|
|
116
|
+
sleep(1);
|
|
117
|
+
}
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
export function generateWsShProbe(ucId, title, postcondition, scenario = "happy") {
|
|
121
|
+
return `#!/usr/bin/env bash
|
|
122
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
123
|
+
# WebSocket postcondition: ${postcondition}
|
|
124
|
+
set -euo pipefail
|
|
125
|
+
WS_URL="\${WS_URL:?WS_URL is required}"
|
|
126
|
+
if command -v wscat &>/dev/null; then
|
|
127
|
+
echo "TODO: assert WebSocket message for ${ucId} [${scenario}]"
|
|
128
|
+
exit 1
|
|
129
|
+
else
|
|
130
|
+
echo "SKIP: wscat not found (npm install -g wscat)"
|
|
131
|
+
exit 0
|
|
132
|
+
fi
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
export function generateLogShProbe(ucId, title, _postcondition, scenario = "happy") {
|
|
136
|
+
return `#!/usr/bin/env bash
|
|
137
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
138
|
+
# Log postcondition: structured log entry must appear after use case execution
|
|
139
|
+
set -euo pipefail
|
|
140
|
+
LOG_FILE="\${LOG_FILE:-/tmp/app.log}"
|
|
141
|
+
EXPECTED_PATTERN="\${LOG_PATTERN:-${ucId}}"
|
|
142
|
+
echo "TODO: implement log assertion for ${ucId} [${scenario}]"
|
|
143
|
+
exit 1
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
export function generateA11yProbe(ucId, title) {
|
|
147
|
+
return `import { test, expect } from '@playwright/test';
|
|
148
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* L2 Harness: ${ucId} — ${title} [Accessibility]
|
|
152
|
+
* Contract: Zero WCAG 2.1 AA violations on the primary use case page.
|
|
153
|
+
*/
|
|
154
|
+
test.describe('${ucId} Accessibility: ${title}', () => {
|
|
155
|
+
test('WCAG 2.1 AA — zero violations', async ({ page }) => {
|
|
156
|
+
await page.goto(process.env['BASE_URL'] ?? '');
|
|
157
|
+
const results = await new AxeBuilder({ page })
|
|
158
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
|
|
159
|
+
.analyze();
|
|
160
|
+
expect(results.violations).toEqual([]);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
`;
|
|
164
|
+
}
|
|
165
|
+
export function generateConsumerContractProbe(ucId, title) {
|
|
166
|
+
return `import { PactV3 } from '@pact-foundation/pact';
|
|
167
|
+
import { join } from 'path';
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* L2 Harness: ${ucId} — ${title} [Consumer Contract]
|
|
171
|
+
*/
|
|
172
|
+
const provider = new PactV3({
|
|
173
|
+
consumer: '{{consumer_name}}',
|
|
174
|
+
provider: '{{provider_name}}',
|
|
175
|
+
dir: join(__dirname, '../../pacts'),
|
|
176
|
+
});
|
|
177
|
+
describe('${ucId} Consumer Contract', () => {
|
|
178
|
+
it('has a valid interaction for ${title}', async () => {
|
|
179
|
+
throw new Error('Not implemented: define the Pact interaction for ${ucId}');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
export function generateProviderContractProbe(ucId, title) {
|
|
185
|
+
return `import { Verifier } from '@pact-foundation/pact';
|
|
186
|
+
import { join } from 'path';
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* L2 Harness: ${ucId} — ${title} [Provider Verification]
|
|
190
|
+
*/
|
|
191
|
+
describe('${ucId} Provider Verification', () => {
|
|
192
|
+
it('satisfies all consumer contracts', async () => {
|
|
193
|
+
return new Verifier({
|
|
194
|
+
provider: '{{provider_name}}',
|
|
195
|
+
providerBaseUrl: process.env.PROVIDER_URL ?? 'http://localhost:3000',
|
|
196
|
+
pactUrls: [join(__dirname, '../../pacts')],
|
|
197
|
+
}).verifyProvider();
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
export function generateGrpcShProbe(ucId, title, postcondition, scenario = "happy") {
|
|
203
|
+
return `#!/usr/bin/env bash
|
|
204
|
+
# L2 Harness: ${ucId} — ${title} [${scenario}]
|
|
205
|
+
# gRPC postcondition: ${postcondition}
|
|
206
|
+
set -euo pipefail
|
|
207
|
+
GRPC_HOST="\${GRPC_HOST:?GRPC_HOST is required}"
|
|
208
|
+
if ! command -v grpcurl &>/dev/null; then
|
|
209
|
+
echo "SKIP: grpcurl not found (brew install grpcurl)"
|
|
210
|
+
exit 0
|
|
211
|
+
fi
|
|
212
|
+
echo "TODO: implement gRPC probe for ${ucId} [${scenario}]"
|
|
213
|
+
exit 1
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
export function generateZapShProbe(ucId, title) {
|
|
217
|
+
return `#!/usr/bin/env bash
|
|
218
|
+
# L2 Harness: ${ucId} — ${title} [Security Scan]
|
|
219
|
+
# Contract: zero HIGH/CRITICAL vulnerabilities on the use case endpoint.
|
|
220
|
+
set -euo pipefail
|
|
221
|
+
TARGET_URL="\${TARGET_URL:?TARGET_URL is required}"
|
|
222
|
+
if ! command -v docker &>/dev/null; then
|
|
223
|
+
echo "SKIP: docker not found — cannot run OWASP ZAP scan"
|
|
224
|
+
exit 0
|
|
225
|
+
fi
|
|
226
|
+
docker run --rm -t owasp/zap2docker-stable \\
|
|
227
|
+
zap-baseline.py -t "$TARGET_URL" -l WARN --exit-code true 2>&1 || {
|
|
228
|
+
echo "FAIL: ZAP found vulnerabilities on $TARGET_URL"
|
|
229
|
+
exit 1
|
|
230
|
+
}
|
|
231
|
+
`;
|
|
232
|
+
}
|
|
233
|
+
export function generateSimProbe(ucId, title) {
|
|
234
|
+
return `/**
|
|
235
|
+
* L2 Harness: ${ucId} — ${title}
|
|
236
|
+
* Headless simulation probe — verifies behavioral invariants without rendering.
|
|
237
|
+
*/
|
|
238
|
+
throw new Error('Probe not yet implemented — implement the ${ucId} simulation scenario');
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
export function generateProbeContent(ucId, title, probeType, details, scenario = "happy") {
|
|
242
|
+
switch (probeType) {
|
|
243
|
+
case "playwright":
|
|
244
|
+
return generatePlaywrightProbe(ucId, title, details.precondition, details.postcondition, details.steps, scenario);
|
|
245
|
+
case "api_call":
|
|
246
|
+
case "hurl":
|
|
247
|
+
return generateHurlProbe(ucId, title, details.precondition, details.postcondition, details.steps, scenario);
|
|
248
|
+
case "graphql":
|
|
249
|
+
return generateGraphqlHurlProbe(ucId, title, details.postcondition, scenario);
|
|
250
|
+
case "headless_sim":
|
|
251
|
+
return generateSimProbe(ucId, title);
|
|
252
|
+
case "db_query":
|
|
253
|
+
return generateDbShProbe(ucId, title, details.postcondition, scenario);
|
|
254
|
+
case "message_queue":
|
|
255
|
+
return generateMqShProbe(ucId, title, details.postcondition, scenario);
|
|
256
|
+
case "performance":
|
|
257
|
+
return generateK6Probe(ucId, title, details.postcondition, scenario);
|
|
258
|
+
case "websocket":
|
|
259
|
+
return generateWsShProbe(ucId, title, details.postcondition, scenario);
|
|
260
|
+
case "log_assertion":
|
|
261
|
+
return generateLogShProbe(ucId, title, details.postcondition, scenario);
|
|
262
|
+
case "a11y":
|
|
263
|
+
return generateA11yProbe(ucId, title);
|
|
264
|
+
case "contract_consumer":
|
|
265
|
+
return generateConsumerContractProbe(ucId, title);
|
|
266
|
+
case "contract_provider":
|
|
267
|
+
return generateProviderContractProbe(ucId, title);
|
|
268
|
+
case "grpc":
|
|
269
|
+
return generateGrpcShProbe(ucId, title, details.postcondition, scenario);
|
|
270
|
+
case "security_scan":
|
|
271
|
+
return generateZapShProbe(ucId, title);
|
|
272
|
+
case "mcp_call":
|
|
273
|
+
return generateShProbe(ucId, title, details.postcondition, "mcp_call", scenario);
|
|
274
|
+
case "file_system":
|
|
275
|
+
default:
|
|
276
|
+
return generateShProbe(ucId, title, details.postcondition, "file_system", scenario);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=probe-templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probe-templates.js","sourceRoot":"","sources":["../../src/tools/probe-templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* propose_session tool handler.
|
|
3
|
+
*
|
|
4
|
+
* Produces a pre-implementation impact assessment before generate_session_prompt runs.
|
|
5
|
+
* Adapted from OpenSpec's Propose phase, extended with forgecraft's layer-awareness.
|
|
6
|
+
*
|
|
7
|
+
* Output: which specs will change (ADDED/MODIFIED), which layers are affected,
|
|
8
|
+
* which gates must pass before close_cycle, and an implementation checklist.
|
|
9
|
+
* Writes .forgecraft/proposal.md as a persistent artifact.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import type { ToolResult } from "../shared/types.js";
|
|
13
|
+
export declare const proposeSessionSchema: z.ZodObject<{
|
|
14
|
+
project_dir: z.ZodString;
|
|
15
|
+
item_description: z.ZodOptional<z.ZodString>;
|
|
16
|
+
roadmap_item_id: z.ZodOptional<z.ZodString>;
|
|
17
|
+
}, "strip", z.ZodTypeAny, {
|
|
18
|
+
project_dir: string;
|
|
19
|
+
item_description?: string | undefined;
|
|
20
|
+
roadmap_item_id?: string | undefined;
|
|
21
|
+
}, {
|
|
22
|
+
project_dir: string;
|
|
23
|
+
item_description?: string | undefined;
|
|
24
|
+
roadmap_item_id?: string | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
export type ProposeSessionInput = z.infer<typeof proposeSessionSchema>;
|
|
27
|
+
export declare function proposeSessionHandler(args: ProposeSessionInput): Promise<ToolResult>;
|
|
28
|
+
//# sourceMappingURL=propose-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"propose-session.d.ts","sourceRoot":"","sources":["../../src/tools/propose-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAMrD,eAAO,MAAM,oBAAoB;;;;;;;;;;;;EAe/B,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAqBvE,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,UAAU,CAAC,CAqDrB"}
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* propose_session tool handler.
|
|
3
|
+
*
|
|
4
|
+
* Produces a pre-implementation impact assessment before generate_session_prompt runs.
|
|
5
|
+
* Adapted from OpenSpec's Propose phase, extended with forgecraft's layer-awareness.
|
|
6
|
+
*
|
|
7
|
+
* Output: which specs will change (ADDED/MODIFIED), which layers are affected,
|
|
8
|
+
* which gates must pass before close_cycle, and an implementation checklist.
|
|
9
|
+
* Writes .forgecraft/proposal.md as a persistent artifact.
|
|
10
|
+
*/
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, } from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { parseUseCases } from "./layer-status.js";
|
|
15
|
+
import { detectClarificationMarkers } from "./session-prompt-builders.js";
|
|
16
|
+
// ── Schema ────────────────────────────────────────────────────────────
|
|
17
|
+
export const proposeSessionSchema = z.object({
|
|
18
|
+
project_dir: z.string().describe("Absolute path to the project root."),
|
|
19
|
+
item_description: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(10)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("What this session should build or fix. One sentence with actor, behavior, and postcondition."),
|
|
24
|
+
roadmap_item_id: z
|
|
25
|
+
.string()
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Roadmap item ID to propose for (e.g. 'RM-001'). Reads title from docs/roadmap.md."),
|
|
28
|
+
});
|
|
29
|
+
// ── Handler ──────────────────────────────────────────────────────────
|
|
30
|
+
export async function proposeSessionHandler(args) {
|
|
31
|
+
const projectDir = resolve(args.project_dir);
|
|
32
|
+
let itemDescription = args.item_description;
|
|
33
|
+
if (!itemDescription && args.roadmap_item_id) {
|
|
34
|
+
itemDescription =
|
|
35
|
+
resolveRoadmapTitle(projectDir, args.roadmap_item_id) ?? undefined;
|
|
36
|
+
}
|
|
37
|
+
if (!itemDescription) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: "text",
|
|
42
|
+
text: "## Proposal Blocked\n\nProvide `item_description` or a valid `roadmap_item_id`.",
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const useCases = loadUseCases(projectDir);
|
|
48
|
+
const relatedUcIds = matchRelatedUcs(itemDescription, useCases);
|
|
49
|
+
const specDelta = buildSpecDelta(projectDir, itemDescription, relatedUcIds, useCases);
|
|
50
|
+
const gateRequirements = loadRequiredGates(projectDir);
|
|
51
|
+
const clarifications = detectClarificationMarkers(projectDir);
|
|
52
|
+
const layerSummary = buildLayerSummary(projectDir, relatedUcIds, useCases);
|
|
53
|
+
const checklist = buildChecklist(projectDir, itemDescription, specDelta, relatedUcIds);
|
|
54
|
+
const proposal = renderProposal({
|
|
55
|
+
projectDir,
|
|
56
|
+
itemDescription,
|
|
57
|
+
specDelta,
|
|
58
|
+
layerSummary,
|
|
59
|
+
gateRequirements,
|
|
60
|
+
clarifications,
|
|
61
|
+
checklist,
|
|
62
|
+
});
|
|
63
|
+
const forgecraftDir = join(projectDir, ".forgecraft");
|
|
64
|
+
mkdirSync(forgecraftDir, { recursive: true });
|
|
65
|
+
writeFileSync(join(forgecraftDir, "proposal.md"), proposal, "utf-8");
|
|
66
|
+
return { content: [{ type: "text", text: proposal }] };
|
|
67
|
+
}
|
|
68
|
+
// ── Use Case Matching ─────────────────────────────────────────────────
|
|
69
|
+
function loadUseCases(projectDir) {
|
|
70
|
+
const path = join(projectDir, "docs", "use-cases.md");
|
|
71
|
+
if (!existsSync(path))
|
|
72
|
+
return [];
|
|
73
|
+
try {
|
|
74
|
+
return parseUseCases(readFileSync(path, "utf-8"));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function matchRelatedUcs(description, ucs) {
|
|
81
|
+
if (ucs.length === 0)
|
|
82
|
+
return [];
|
|
83
|
+
const lower = description.toLowerCase();
|
|
84
|
+
const words = lower.split(/\s+/).filter((w) => w.length > 3);
|
|
85
|
+
return ucs
|
|
86
|
+
.filter((uc) => {
|
|
87
|
+
const title = uc.title.toLowerCase();
|
|
88
|
+
return words.some((w) => title.includes(w));
|
|
89
|
+
})
|
|
90
|
+
.map((uc) => uc.id);
|
|
91
|
+
}
|
|
92
|
+
// ── Spec Delta ────────────────────────────────────────────────────────
|
|
93
|
+
function buildSpecDelta(projectDir, description, relatedUcIds, ucs) {
|
|
94
|
+
const deltas = [];
|
|
95
|
+
// Use cases
|
|
96
|
+
if (existsSync(join(projectDir, "docs", "use-cases.md"))) {
|
|
97
|
+
if (relatedUcIds.length > 0) {
|
|
98
|
+
deltas.push({
|
|
99
|
+
artifact: "docs/use-cases.md",
|
|
100
|
+
change: "MODIFIED",
|
|
101
|
+
reason: `Affects use case(s): ${relatedUcIds.join(", ")}`,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else if (ucs.length > 0) {
|
|
105
|
+
deltas.push({
|
|
106
|
+
artifact: "docs/use-cases.md",
|
|
107
|
+
change: "MODIFIED",
|
|
108
|
+
reason: "May require a new use case entry for this session",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
deltas.push({
|
|
114
|
+
artifact: "docs/use-cases.md",
|
|
115
|
+
change: "ADDED",
|
|
116
|
+
reason: "Use cases file does not exist — create before implementing",
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// PRD
|
|
120
|
+
if (!existsSync(join(projectDir, "docs", "PRD.md"))) {
|
|
121
|
+
deltas.push({
|
|
122
|
+
artifact: "docs/PRD.md",
|
|
123
|
+
change: "ADDED",
|
|
124
|
+
reason: "Product requirements not found — run setup_project",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
deltas.push({
|
|
129
|
+
artifact: "docs/PRD.md",
|
|
130
|
+
change: "MODIFIED",
|
|
131
|
+
reason: "Requirement coverage may need updating after session",
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// ADR — check if a related ADR likely exists
|
|
135
|
+
const adrHint = needsAdr(description);
|
|
136
|
+
if (adrHint) {
|
|
137
|
+
deltas.push({
|
|
138
|
+
artifact: "docs/adrs/NNNN-<decision-slug>.md",
|
|
139
|
+
change: "ADDED",
|
|
140
|
+
reason: adrHint,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
// Session prompt
|
|
144
|
+
deltas.push({
|
|
145
|
+
artifact: ".forgecraft/proposal.md",
|
|
146
|
+
change: "ADDED",
|
|
147
|
+
reason: "This proposal (written now)",
|
|
148
|
+
});
|
|
149
|
+
return deltas;
|
|
150
|
+
}
|
|
151
|
+
function needsAdr(description) {
|
|
152
|
+
const triggers = [
|
|
153
|
+
["database", "DB", "storage", "schema", "migration"],
|
|
154
|
+
["auth", "authentication", "authorization", "JWT", "token"],
|
|
155
|
+
["api", "endpoint", "REST", "GraphQL", "gRPC"],
|
|
156
|
+
["cache", "Redis", "CDN"],
|
|
157
|
+
["queue", "event", "message", "Kafka", "SQS"],
|
|
158
|
+
["deploy", "infrastructure", "IaC", "container", "Docker", "Kubernetes"],
|
|
159
|
+
];
|
|
160
|
+
const lower = description.toLowerCase();
|
|
161
|
+
for (const group of triggers) {
|
|
162
|
+
if (group.some((kw) => lower.includes(kw.toLowerCase()))) {
|
|
163
|
+
return `Decision involves ${group[0]} — likely needs an ADR before committing`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
// ── Gate Loading ──────────────────────────────────────────────────────
|
|
169
|
+
function loadRequiredGates(projectDir) {
|
|
170
|
+
const dirs = [
|
|
171
|
+
join(projectDir, ".forgecraft", "gates", "active"),
|
|
172
|
+
join(projectDir, ".forgecraft", "gates", "project", "active"),
|
|
173
|
+
];
|
|
174
|
+
const gates = [];
|
|
175
|
+
for (const dir of dirs) {
|
|
176
|
+
if (!existsSync(dir))
|
|
177
|
+
continue;
|
|
178
|
+
try {
|
|
179
|
+
for (const f of readdirSync(dir).filter((n) => n.endsWith(".yaml"))) {
|
|
180
|
+
const gate = parseGateYaml(join(dir, f));
|
|
181
|
+
if (gate)
|
|
182
|
+
gates.push(gate);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
/* skip */
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return gates;
|
|
190
|
+
}
|
|
191
|
+
function parseGateYaml(filePath) {
|
|
192
|
+
try {
|
|
193
|
+
const text = readFileSync(filePath, "utf-8");
|
|
194
|
+
const id = /^id:\s*(.+)$/m.exec(text)?.[1]?.trim() ?? "";
|
|
195
|
+
const description = /^description:\s*(.+)$/m.exec(text)?.[1]?.trim() ?? "";
|
|
196
|
+
const priority = /^priority:\s*(.+)$/m.exec(text)?.[1]?.trim() ?? "P2";
|
|
197
|
+
const layer = /^layer:\s*(.+)$/m.exec(text)?.[1]?.trim() ?? "L1";
|
|
198
|
+
if (!id)
|
|
199
|
+
return null;
|
|
200
|
+
return { gateId: id, description, priority, layer };
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function buildLayerSummary(projectDir, relatedUcIds, ucs) {
|
|
207
|
+
const harnessDir = join(projectDir, "tests", "harness");
|
|
208
|
+
const envDir = join(projectDir, "tests", "env");
|
|
209
|
+
const sloDir = join(projectDir, "tests", "slo");
|
|
210
|
+
const candidates = relatedUcIds.length > 0
|
|
211
|
+
? ucs.filter((uc) => relatedUcIds.includes(uc.id))
|
|
212
|
+
: ucs.slice(0, 5); // show first 5 if no match
|
|
213
|
+
return candidates.map((uc) => {
|
|
214
|
+
const lower = uc.id.toLowerCase().replace(/_/g, "-");
|
|
215
|
+
const l2 = existsSync(harnessDir) &&
|
|
216
|
+
readdirSync(harnessDir).some((f) => f.toLowerCase().startsWith(lower))
|
|
217
|
+
? "✅ probes found"
|
|
218
|
+
: "❌ no probes";
|
|
219
|
+
const l3 = existsSync(envDir) &&
|
|
220
|
+
readdirSync(envDir).some((f) => f.toLowerCase().startsWith(lower))
|
|
221
|
+
? "✅ env probes"
|
|
222
|
+
: "—";
|
|
223
|
+
const l4 = existsSync(sloDir) &&
|
|
224
|
+
readdirSync(sloDir).some((f) => f.toLowerCase().startsWith(lower))
|
|
225
|
+
? "✅ slo probes"
|
|
226
|
+
: "—";
|
|
227
|
+
const l1 = existsSync(join(projectDir, "docs", "use-cases.md"))
|
|
228
|
+
? "✅ use case"
|
|
229
|
+
: "❌ missing";
|
|
230
|
+
return { ucId: uc.id, title: uc.title, l1, l2, l3, l4 };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
// ── Checklist ─────────────────────────────────────────────────────────
|
|
234
|
+
function buildChecklist(projectDir, _description, specDelta, relatedUcIds) {
|
|
235
|
+
const items = [];
|
|
236
|
+
// Cascade check
|
|
237
|
+
items.push("Run `check_cascade` — all 5 steps must be complete before starting");
|
|
238
|
+
// Clarifications
|
|
239
|
+
items.push("Resolve all `[NEEDS CLARIFICATION]` markers in spec artifacts");
|
|
240
|
+
// Spec updates
|
|
241
|
+
const added = specDelta.filter((d) => d.change === "ADDED");
|
|
242
|
+
if (added.length > 0) {
|
|
243
|
+
for (const d of added) {
|
|
244
|
+
items.push(`Create missing artifact: \`${d.artifact}\` — ${d.reason}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Harness
|
|
248
|
+
if (relatedUcIds.length > 0) {
|
|
249
|
+
items.push(`Run \`generate_harness\` for affected use cases: ${relatedUcIds.join(", ")}`);
|
|
250
|
+
items.push("Implement harness probe TODO sections before closing");
|
|
251
|
+
}
|
|
252
|
+
// TDD loop
|
|
253
|
+
items.push("Write failing probes first, then implement until green");
|
|
254
|
+
items.push("Run `run_harness` — all L2 probes must pass before `close_cycle`");
|
|
255
|
+
// Env / infra check
|
|
256
|
+
if (existsSync(join(projectDir, "tests", "env"))) {
|
|
257
|
+
items.push("Run `run_env_probe` — L3 env contracts must hold after changes");
|
|
258
|
+
}
|
|
259
|
+
// Close
|
|
260
|
+
items.push("Run `close_cycle` — evaluates all gates and writes `.claude/state.md`");
|
|
261
|
+
items.push("Update `Status.md` with completed changes and next steps");
|
|
262
|
+
return items;
|
|
263
|
+
}
|
|
264
|
+
// ── Roadmap Resolution ────────────────────────────────────────────────
|
|
265
|
+
function resolveRoadmapTitle(projectDir, itemId) {
|
|
266
|
+
const path = join(projectDir, "docs", "roadmap.md");
|
|
267
|
+
if (!existsSync(path))
|
|
268
|
+
return null;
|
|
269
|
+
const text = readFileSync(path, "utf-8");
|
|
270
|
+
const re = new RegExp(`\\|\\s*${itemId}\\s*\\|[^|]+\\|([^|]+)\\|`);
|
|
271
|
+
const m = re.exec(text);
|
|
272
|
+
return m ? (m[1]?.trim() ?? null) : null;
|
|
273
|
+
}
|
|
274
|
+
function renderProposal(input) {
|
|
275
|
+
const { itemDescription, specDelta, layerSummary, gateRequirements, clarifications, checklist, } = input;
|
|
276
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
277
|
+
const lines = [];
|
|
278
|
+
lines.push(`# Session Proposal`, ``, `**Date:** ${date}`, `**Scope:** ${itemDescription}`, ``);
|
|
279
|
+
lines.push(`> This proposal is a pre-implementation impact assessment.`);
|
|
280
|
+
lines.push(`> Resolve all blockers, then run \`generate_session_prompt\` to commit to implementation.`, ``);
|
|
281
|
+
// Spec delta
|
|
282
|
+
lines.push(`## Spec Delta`, ``);
|
|
283
|
+
lines.push(`| Artifact | Change | Reason |`, `|---|---|---|`);
|
|
284
|
+
for (const d of specDelta) {
|
|
285
|
+
const badge = d.change === "ADDED"
|
|
286
|
+
? "🆕 ADDED"
|
|
287
|
+
: d.change === "MODIFIED"
|
|
288
|
+
? "✏️ MODIFIED"
|
|
289
|
+
: "🗑 REMOVED";
|
|
290
|
+
lines.push(`| \`${d.artifact}\` | ${badge} | ${d.reason} |`);
|
|
291
|
+
}
|
|
292
|
+
lines.push(``);
|
|
293
|
+
// Layer readiness
|
|
294
|
+
if (layerSummary.length > 0) {
|
|
295
|
+
lines.push(`## Layer Readiness — Affected Use Cases`, ``);
|
|
296
|
+
lines.push(`| UC | Title | L1 Spec | L2 Probes | L3 Env | L4 SLO |`, `|---|---|---|---|---|---|`);
|
|
297
|
+
for (const r of layerSummary) {
|
|
298
|
+
lines.push(`| ${r.ucId} | ${r.title} | ${r.l1} | ${r.l2} | ${r.l3} | ${r.l4} |`);
|
|
299
|
+
}
|
|
300
|
+
lines.push(``);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
lines.push(`## Layer Readiness`, ``, `_No use cases found. Run \`setup_project\` to create the spec foundation._`, ``);
|
|
304
|
+
}
|
|
305
|
+
// Clarifications
|
|
306
|
+
if (clarifications.length > 0) {
|
|
307
|
+
lines.push(`## ⚠️ Unresolved Clarifications — Must Resolve Before Session`, ``);
|
|
308
|
+
lines.push(`| File | Marker |`, `|---|---|`);
|
|
309
|
+
for (const c of clarifications) {
|
|
310
|
+
lines.push(`| \`${c.file}\` | ${c.marker} |`);
|
|
311
|
+
}
|
|
312
|
+
lines.push(``, `Resolve these before running \`generate_session_prompt\`.`, ``);
|
|
313
|
+
}
|
|
314
|
+
// Gates
|
|
315
|
+
if (gateRequirements.length > 0) {
|
|
316
|
+
lines.push(`## Gates That Must Pass Before \`close_cycle\``, ``);
|
|
317
|
+
lines.push(`| Gate ID | Layer | Priority | Description |`, `|---|---|---|---|`);
|
|
318
|
+
for (const g of gateRequirements) {
|
|
319
|
+
lines.push(`| ${g.gateId} | ${g.layer} | ${g.priority} | ${g.description} |`);
|
|
320
|
+
}
|
|
321
|
+
lines.push(``);
|
|
322
|
+
}
|
|
323
|
+
// Checklist
|
|
324
|
+
lines.push(`## Pre-Implementation Checklist`, ``);
|
|
325
|
+
for (const item of checklist) {
|
|
326
|
+
lines.push(`- [ ] ${item}`);
|
|
327
|
+
}
|
|
328
|
+
lines.push(``);
|
|
329
|
+
lines.push(`---`);
|
|
330
|
+
lines.push(`_Proposal written to \`.forgecraft/proposal.md\`. Run \`generate_session_prompt\` when ready to commit._`);
|
|
331
|
+
return lines.join("\n");
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=propose-session.js.map
|