rcf-lite 0.0.1 → 0.7.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/CHANGELOG.md +288 -0
- package/LICENSE +202 -0
- package/README.md +92 -4
- package/bin/rcf-verify.js +122 -0
- package/bin/rcf.js +174 -0
- package/bin/view-supervisor-child.mjs +14 -0
- package/fixtures/canary-manifest.json +103 -0
- package/guidance/README.md +25 -0
- package/guidance/build-cycle-playbook.md +492 -0
- package/guidance/build-cycle.md +40 -0
- package/guidance/document-model.md +45 -0
- package/guidance/elicitation-playbook.md +442 -0
- package/guidance/harness-template.md +246 -0
- package/guidance/managed/README.md +63 -0
- package/guidance/managed/agent-instructions-block.hash +1 -0
- package/guidance/managed/agent-instructions-block.md +220 -0
- package/guidance/managed/legacy-fragment-hashes.json +22 -0
- package/guidance/manifest.json +21 -0
- package/guidance/overview.md +48 -0
- package/guidance/persona-programme.md +44 -0
- package/package.json +69 -8
- package/rcf/adrs/adr-001.json +25 -0
- package/rcf/adrs/adr-002.json +25 -0
- package/rcf/adrs/adr-003.json +25 -0
- package/rcf/adrs/adr-004.json +20 -0
- package/rcf/adrs/adr-005.json +20 -0
- package/rcf/adrs/adr-006.json +25 -0
- package/rcf/adrs/adr-007.json +25 -0
- package/rcf/adrs/adr-008.json +25 -0
- package/rcf/adrs/adr-009.json +25 -0
- package/rcf/build-sequence.json +11 -0
- package/rcf/code-nodes/cn-001.json +14 -0
- package/rcf/code-nodes/cn-002.json +14 -0
- package/rcf/code-nodes/cn-003.json +15 -0
- package/rcf/code-nodes/cn-004.json +17 -0
- package/rcf/code-nodes/cn-005.json +19 -0
- package/rcf/code-nodes/cn-006.json +17 -0
- package/rcf/code-nodes/cn-007.json +16 -0
- package/rcf/code-nodes/cn-008.json +19 -0
- package/rcf/code-nodes/cn-009.json +16 -0
- package/rcf/code-nodes/cn-010.json +18 -0
- package/rcf/code-nodes/cn-011.json +18 -0
- package/rcf/code-nodes/cn-012.json +18 -0
- package/rcf/code-nodes/cn-013.json +19 -0
- package/rcf/code-nodes/cn-014.json +16 -0
- package/rcf/code-nodes/cn-015.json +16 -0
- package/rcf/code-nodes/cn-016.json +16 -0
- package/rcf/code-nodes/cn-017.json +16 -0
- package/rcf/code-nodes/cn-018.json +15 -0
- package/rcf/code-nodes/cn-019.json +16 -0
- package/rcf/code-nodes/cn-020.json +16 -0
- package/rcf/code-nodes/cn-021.json +16 -0
- package/rcf/code-nodes/cn-022.json +16 -0
- package/rcf/code-nodes/cn-023.json +16 -0
- package/rcf/code-nodes/cn-024.json +16 -0
- package/rcf/code-nodes/cn-025.json +16 -0
- package/rcf/code-nodes/cn-026.json +16 -0
- package/rcf/code-nodes/cn-027.json +16 -0
- package/rcf/code-nodes/cn-028.json +16 -0
- package/rcf/code-nodes/cn-029.json +19 -0
- package/rcf/code-nodes/cn-030.json +14 -0
- package/rcf/code-nodes/cn-031.json +14 -0
- package/rcf/code-nodes/cn-032.json +14 -0
- package/rcf/code-nodes/cn-033.json +14 -0
- package/rcf/code-nodes/cn-034.json +14 -0
- package/rcf/code-nodes/cn-035.json +14 -0
- package/rcf/code-nodes/cn-036.json +14 -0
- package/rcf/code-nodes/cn-037.json +14 -0
- package/rcf/code-nodes/cn-038.json +14 -0
- package/rcf/code-nodes/cn-039.json +14 -0
- package/rcf/code-nodes/cn-040.json +14 -0
- package/rcf/code-nodes/cn-041.json +14 -0
- package/rcf/code-nodes/cn-042.json +14 -0
- package/rcf/code-nodes/cn-043.json +14 -0
- package/rcf/code-nodes/cn-044.json +14 -0
- package/rcf/code-nodes/cn-045.json +14 -0
- package/rcf/code-nodes/cn-046.json +14 -0
- package/rcf/code-nodes/cn-047.json +14 -0
- package/rcf/code-nodes/cn-048.json +14 -0
- package/rcf/code-nodes/cn-049.json +14 -0
- package/rcf/code-nodes/cn-050.json +14 -0
- package/rcf/code-nodes/cn-051.json +14 -0
- package/rcf/code-nodes/cn-052.json +14 -0
- package/rcf/code-nodes/cn-053.json +14 -0
- package/rcf/code-nodes/cn-054.json +14 -0
- package/rcf/code-nodes/cn-055.json +14 -0
- package/rcf/code-nodes/cn-056.json +14 -0
- package/rcf/code-nodes/cn-057.json +14 -0
- package/rcf/fbs/fbs-001.json +49 -0
- package/rcf/fbs/fbs-002.json +42 -0
- package/rcf/fbs/fbs-003.json +37 -0
- package/rcf/fbs/fbs-004.json +39 -0
- package/rcf/fbs/fbs-005.json +38 -0
- package/rcf/fbs/fbs-006.json +48 -0
- package/rcf/fbs/fbs-007.json +39 -0
- package/rcf/fbs/fbs-008.json +40 -0
- package/rcf/fbs/fbs-009.json +36 -0
- package/rcf/fbs/fbs-010.json +41 -0
- package/rcf/fbs/fbs-011.json +36 -0
- package/rcf/fbs/fbs-012.json +46 -0
- package/rcf/fbs/fbs-013.json +42 -0
- package/rcf/fbs/fbs-014.json +49 -0
- package/rcf/fbs/fbs-015.json +32 -0
- package/rcf/manifest.json +17 -0
- package/rcf/prd.json +47 -0
- package/rcf/requirements/req-001.json +19 -0
- package/rcf/requirements/req-002.json +19 -0
- package/rcf/requirements/req-003.json +19 -0
- package/rcf/requirements/req-004.json +19 -0
- package/rcf/requirements/req-005.json +19 -0
- package/rcf/requirements/req-006.json +19 -0
- package/rcf/requirements/req-007.json +19 -0
- package/rcf/requirements/req-008.json +19 -0
- package/rcf/requirements/req-009.json +19 -0
- package/rcf/tacs/tac-001.json +45 -0
- package/rcf/tacs/tac-002.json +109 -0
- package/rcf/tacs/tac-003.json +40 -0
- package/rcf/tacs/tac-004.json +51 -0
- package/rcf/tacs/tac-005.json +52 -0
- package/rcf/tacs/tac-006.json +104 -0
- package/rcf/tacs/tac-007.json +38 -0
- package/rcf/tacs/tac-008.json +51 -0
- package/rcf/tad.json +51 -0
- package/rcf/test-suites/PENDING.md +23 -0
- package/rcf/test-suites/ts-001.json +38 -0
- package/rcf/test-suites/ts-002.json +38 -0
- package/rcf/test-suites/ts-003.json +43 -0
- package/rcf/test-suites/ts-004.json +44 -0
- package/rcf/test-suites/ts-005.json +30 -0
- package/rcf/test-suites/ts-006.json +36 -0
- package/rcf/test-suites/ts-007.json +43 -0
- package/rcf/test-suites/ts-008.json +37 -0
- package/rcf/test-suites/ts-009.json +38 -0
- package/rcf/test-suites/ts-010.json +38 -0
- package/rcf/test-suites/ts-011.json +44 -0
- package/rcf/test-suites/ts-012.json +36 -0
- package/rcf/test-suites/ts-013.json +38 -0
- package/rcf/test-suites/ts-014.json +38 -0
- package/rcf/test-suites/ts-015.json +38 -0
- package/rcf/test-suites/ts-016.json +37 -0
- package/rcf/test-suites/ts-017.json +38 -0
- package/rcf/test-suites/ts-018.json +38 -0
- package/rcf/test-suites/ts-019.json +37 -0
- package/rcf/test-suites/ts-020.json +46 -0
- package/rcf/test-suites/ts-021.json +46 -0
- package/rcf/test-suites/ts-022.json +46 -0
- package/rcf/test-suites/ts-023.json +46 -0
- package/rcf/test-suites/ts-024.json +46 -0
- package/rcf/test-suites/ts-025.json +52 -0
- package/rcf/user-stories/us-101.json +40 -0
- package/rcf/user-stories/us-102.json +40 -0
- package/rcf/user-stories/us-201.json +40 -0
- package/rcf/user-stories/us-202.json +40 -0
- package/rcf/user-stories/us-203.json +32 -0
- package/rcf/user-stories/us-301.json +40 -0
- package/rcf/user-stories/us-302.json +40 -0
- package/rcf/user-stories/us-303.json +40 -0
- package/rcf/user-stories/us-304.json +40 -0
- package/rcf/user-stories/us-401.json +40 -0
- package/rcf/user-stories/us-402.json +40 -0
- package/rcf/user-stories/us-403.json +40 -0
- package/rcf/user-stories/us-501.json +40 -0
- package/rcf/user-stories/us-502.json +40 -0
- package/rcf/user-stories/us-503.json +40 -0
- package/rcf/user-stories/us-601.json +40 -0
- package/rcf/user-stories/us-602.json +40 -0
- package/rcf/user-stories/us-701.json +40 -0
- package/rcf/user-stories/us-702.json +40 -0
- package/rcf/user-stories/us-801.json +49 -0
- package/rcf/user-stories/us-802.json +49 -0
- package/rcf/user-stories/us-803.json +49 -0
- package/rcf/user-stories/us-804.json +49 -0
- package/rcf/user-stories/us-805.json +49 -0
- package/rcf/user-stories/us-901.json +40 -0
- package/src/.gitkeep +0 -0
- package/src/browser-verify/auth-smoke.js +109 -0
- package/src/browser-verify/index.js +29 -0
- package/src/browser-verify/invariants.js +336 -0
- package/src/browser-verify/manifest-writer.js +189 -0
- package/src/browser-verify/runner.js +171 -0
- package/src/build/bundle.js +198 -0
- package/src/build/formatters/json.js +18 -0
- package/src/build/formatters/markdown.js +406 -0
- package/src/build/index.js +14 -0
- package/src/build/mark.js +177 -0
- package/src/build/queue.js +285 -0
- package/src/cli/browser-verify.js +231 -0
- package/src/cli/build.js +584 -0
- package/src/cli/coverage.js +219 -0
- package/src/cli/create.js +355 -0
- package/src/cli/delete.js +127 -0
- package/src/cli/design.js +353 -0
- package/src/cli/doctor.js +548 -0
- package/src/cli/fbs.js +171 -0
- package/src/cli/finalise.js +377 -0
- package/src/cli/guidance.js +206 -0
- package/src/cli/help.js +156 -0
- package/src/cli/impact.js +119 -0
- package/src/cli/init.js +282 -0
- package/src/cli/intake.js +153 -0
- package/src/cli/link.js +128 -0
- package/src/cli/mcp.js +160 -0
- package/src/cli/preflight.js +220 -0
- package/src/cli/read.js +162 -0
- package/src/cli/req-baseline.js +269 -0
- package/src/cli/req-classify.js +135 -0
- package/src/cli/review.js +295 -0
- package/src/cli/test-suite.js +216 -0
- package/src/cli/trace.js +172 -0
- package/src/cli/ui-baseline.js +292 -0
- package/src/cli/ui-classify.js +108 -0
- package/src/cli/update.js +197 -0
- package/src/cli/validate.js +168 -0
- package/src/cli/view.js +452 -0
- package/src/core/baseline-catalog/data/auth.json +42 -0
- package/src/core/baseline-catalog/data/http-api.json +42 -0
- package/src/core/baseline-catalog/data/notifications.json +33 -0
- package/src/core/baseline-catalog/data/persistence.json +33 -0
- package/src/core/baseline-catalog/data/web-ui.json +60 -0
- package/src/core/baseline-catalog/index.js +121 -0
- package/src/core/errors/index.js +167 -0
- package/src/core/fixtures/register-canary/canary-prompt-01.json +18 -0
- package/src/core/fixtures/register-canary/canary-prompt-02.json +21 -0
- package/src/core/fixtures/register-canary/canary-prompt-03.json +17 -0
- package/src/core/isolation/index.js +60 -0
- package/src/core/mcp/framing.js +103 -0
- package/src/core/mcp/index.js +8 -0
- package/src/core/mcp/server.js +228 -0
- package/src/core/patterns/register-canary.js +209 -0
- package/src/core/patterns/req-shapes.js +158 -0
- package/src/core/patterns/services.js +358 -0
- package/src/core/patterns/ui-shapes.js +166 -0
- package/src/core/store/cn-resolve.js +134 -0
- package/src/core/store/derive-deps.js +93 -0
- package/src/core/store/ids.js +78 -0
- package/src/core/store/index.js +20 -0
- package/src/core/store/init.js +255 -0
- package/src/core/store/loader.js +211 -0
- package/src/core/store/tp-resolve.js +176 -0
- package/src/core/store/validator.js +191 -0
- package/src/core/store/walker.js +898 -0
- package/src/core/store/writer.js +1849 -0
- package/src/design/index.js +11 -0
- package/src/design/writer.js +271 -0
- package/src/finalise/detect.js +129 -0
- package/src/finalise/index.js +17 -0
- package/src/finalise/ingest.js +119 -0
- package/src/finalise/install.js +119 -0
- package/src/finalise/ship-without-verified.js +131 -0
- package/src/finalise/spawn.js +84 -0
- package/src/intake/fidelity.js +105 -0
- package/src/intake/index.js +6 -0
- package/src/intake/manifest-writer.js +100 -0
- package/src/intake/orchestrator.js +138 -0
- package/src/intake/validate.js +80 -0
- package/src/mcp/map-errors.js +131 -0
- package/src/mcp/prompts.js +49 -0
- package/src/mcp/resources.js +244 -0
- package/src/mcp/tools.js +1204 -0
- package/src/preflight/design-shapes.js +185 -0
- package/src/preflight/index.js +90 -0
- package/src/preflight/manifest-writer.js +264 -0
- package/src/preflight/scanner.js +206 -0
- package/src/preflight/secrets.js +134 -0
- package/src/preflight/session.js +246 -0
- package/src/query/attestation.js +285 -0
- package/src/query/coverage.js +308 -0
- package/src/query/formatters/json.js +21 -0
- package/src/query/formatters/mermaid.js +209 -0
- package/src/query/formatters/table.js +203 -0
- package/src/query/impact.js +173 -0
- package/src/query/index.js +9 -0
- package/src/query/trace.js +345 -0
- package/src/register-canary/fixture-loader.js +87 -0
- package/src/register-canary/index.js +10 -0
- package/src/register-canary/record-writer.js +132 -0
- package/src/register-canary/runner.js +156 -0
- package/src/req-baseline/gate.js +86 -0
- package/src/req-baseline/index.js +27 -0
- package/src/req-baseline/open-candidates.js +143 -0
- package/src/req-baseline/opt-out.js +195 -0
- package/src/req-baseline/sweep.js +230 -0
- package/src/req-detection/classifier.js +181 -0
- package/src/req-detection/index.js +9 -0
- package/src/req-detection/persist.js +55 -0
- package/src/review/index.js +325 -0
- package/src/review/mutation.js +117 -0
- package/src/review/ui-baseline-drift.js +138 -0
- package/src/server/index.js +178 -0
- package/src/server/routes.js +110 -0
- package/src/server/sse.js +118 -0
- package/src/setup/agent-setup.js +362 -0
- package/src/setup/identity-seed.js +104 -0
- package/src/setup/knowledge-seed.js +123 -0
- package/src/setup/managed-block.js +193 -0
- package/src/setup/managed-gitignore.js +166 -0
- package/src/setup/managed-markers.js +49 -0
- package/src/ui-baseline/defaults.js +119 -0
- package/src/ui-baseline/index.js +25 -0
- package/src/ui-baseline/manifest-writer.js +282 -0
- package/src/ui-baseline/session.js +178 -0
- package/src/ui-detection/classifier.js +192 -0
- package/src/verify/chain/index.js +190 -0
- package/src/verify/cli/cleanup.js +61 -0
- package/src/verify/cli/help.js +56 -0
- package/src/verify/cli/mcp.js +98 -0
- package/src/verify/cli/provision.js +71 -0
- package/src/verify/cli/report.js +71 -0
- package/src/verify/cli/run.js +155 -0
- package/src/verify/engine/brief.js +87 -0
- package/src/verify/engine/index.js +177 -0
- package/src/verify/engine/launcher.js +307 -0
- package/src/verify/mcp/tools.js +107 -0
- package/src/verify/profile/index.js +146 -0
- package/src/verify/provision/index.js +256 -0
- package/src/verify/report/index.js +139 -0
- package/src/verify/report/renderer.js +118 -0
- package/src/verify/verdict/index.js +246 -0
- package/src/view/doc-renderers/adr.js +44 -0
- package/src/view/doc-renderers/build-sequence.js +40 -0
- package/src/view/doc-renderers/fbs.js +128 -0
- package/src/view/doc-renderers/helpers.js +159 -0
- package/src/view/doc-renderers/index.js +12 -0
- package/src/view/doc-renderers/prd.js +45 -0
- package/src/view/doc-renderers/req.js +43 -0
- package/src/view/doc-renderers/tac.js +38 -0
- package/src/view/doc-renderers/tad.js +74 -0
- package/src/view/doc-renderers/test-suite.js +45 -0
- package/src/view/doc-renderers/user-story.js +63 -0
- package/src/view/html-page.js +462 -0
- package/src/view/index.js +63 -0
- package/src/view/live-client.js +338 -0
- package/src/view/mermaid-diagram.js +178 -0
- package/src/view/style.css +735 -0
- package/src/view/tree-model.js +152 -0
- package/src/view/vendored/mermaid.min.js +2607 -0
- package/src/view-supervisor/index.js +26 -0
- package/src/view-supervisor/logs.js +32 -0
- package/src/view-supervisor/manifest-writer.js +178 -0
- package/src/view-supervisor/persist-until.js +85 -0
- package/src/view-supervisor/supervisor.js +276 -0
- package/src/watch/index.js +152 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// The D10 / D11 mapping: RcfError lists, writer refusals and unknown
|
|
2
|
+
// ids become MCP tool execution errors (`isError: true`) carrying the
|
|
3
|
+
// validate-shaped payload `{ok: false, errors: [{id, kind, rule,
|
|
4
|
+
// filePath, field, message}]}` - the same per-issue grammar `rcf
|
|
5
|
+
// validate --json` ships, so agents parse exactly one error shape
|
|
6
|
+
// across the whole surface (D11). Unexpected failures keep their
|
|
7
|
+
// stacks on stderr, never in model context (D10).
|
|
8
|
+
|
|
9
|
+
import { formatError, rcfError } from '#core/errors';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Map one RcfError to the validate --json issue shape (nullable
|
|
13
|
+
* id / rule / filePath / field). Mirrors src/cli/validate.js.
|
|
14
|
+
*
|
|
15
|
+
* @param {import('#core/errors').RcfError} e
|
|
16
|
+
* @returns {{id: string|null, kind: string, rule: string|null, filePath: string|null, field: string|null, message: string}}
|
|
17
|
+
*/
|
|
18
|
+
export function issueFromRcfError(e) {
|
|
19
|
+
return {
|
|
20
|
+
id: e.documentId ?? null,
|
|
21
|
+
kind: e.kind,
|
|
22
|
+
rule: e.rule ?? null,
|
|
23
|
+
filePath: e.filePath ?? null,
|
|
24
|
+
field: e.field ?? null,
|
|
25
|
+
message: e.message,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Map a list of RcfErrors to the issues array.
|
|
31
|
+
*
|
|
32
|
+
* @param {import('#core/errors').RcfError[]} errors
|
|
33
|
+
* @returns {ReturnType<typeof issueFromRcfError>[]}
|
|
34
|
+
*/
|
|
35
|
+
export function issuesFromErrors(errors) {
|
|
36
|
+
return errors.map(issueFromRcfError);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a tool execution error result from structured errors. The text
|
|
41
|
+
* block carries the human-readable line(s) via the existing
|
|
42
|
+
* formatError helper (D11); the structured payload is the validate
|
|
43
|
+
* issue shape.
|
|
44
|
+
*
|
|
45
|
+
* @param {import('#core/errors').RcfError[]} errors
|
|
46
|
+
* @returns {object} tools/call result with isError: true
|
|
47
|
+
*/
|
|
48
|
+
export function errorResult(errors) {
|
|
49
|
+
const text = errors.map((e) => formatError(e)).join('\n');
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text }],
|
|
52
|
+
structuredContent: { ok: false, errors: issuesFromErrors(errors) },
|
|
53
|
+
isError: true,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Single-cause usage error (unknown id, bad argument, refused scope) -
|
|
59
|
+
* a one-element errors array per D11.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} message
|
|
62
|
+
* @param {object} [extras] - optional documentId / field / rule
|
|
63
|
+
* @returns {object}
|
|
64
|
+
*/
|
|
65
|
+
export function usageErrorResult(message, extras = {}) {
|
|
66
|
+
return errorResult([rcfError({ kind: 'usage', message, ...extras })]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* D10 row: walker errors block a QUERY. The tree is broken; the agent's
|
|
71
|
+
* next move is fixing it, so it gets the full validate-shaped issue
|
|
72
|
+
* list. Write tools no longer route here (B5): they proceed and gate on
|
|
73
|
+
* the post-write tree state instead - that is how a broken tree gets
|
|
74
|
+
* fixed in-tool.
|
|
75
|
+
*
|
|
76
|
+
* @param {import('#core/errors').RcfError[]} errors
|
|
77
|
+
* @returns {object}
|
|
78
|
+
*/
|
|
79
|
+
export function walkerBlockedResult(errors) {
|
|
80
|
+
return errorResult(errors);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* D10 row: unexpected / IO failure. Message only into model context;
|
|
85
|
+
* the full stack goes to stderr via the injected logger - mirrors
|
|
86
|
+
* writeUnexpectedFailure without leaking stacks into the conversation.
|
|
87
|
+
*
|
|
88
|
+
* @param {import('#core/errors').RcfError} err
|
|
89
|
+
* @param {{error: (line: string) => void}} log
|
|
90
|
+
* @returns {object}
|
|
91
|
+
*/
|
|
92
|
+
export function unexpectedFailureResult(err, log) {
|
|
93
|
+
log.error(`[rcf mcp] unexpected failure: ${err.message}\n${err.stack ?? ''}`);
|
|
94
|
+
const stripped = rcfError({
|
|
95
|
+
kind: 'ioFailure',
|
|
96
|
+
message: err.message,
|
|
97
|
+
...(err.documentId !== undefined ? { documentId: err.documentId } : {}),
|
|
98
|
+
...(err.filePath !== undefined ? { filePath: err.filePath } : {}),
|
|
99
|
+
});
|
|
100
|
+
return errorResult([stripped]);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Map a structured writer return (RcfError) to the right D10 row:
|
|
105
|
+
* - ioFailure -> unexpected-failure handling (stack to stderr)
|
|
106
|
+
* - usage + dependents / wouldOrphan rule -> refusal (exit-4 twin);
|
|
107
|
+
* message names the dependents and the cascade remedy
|
|
108
|
+
* - everything else -> plain execution error with the D11 payload
|
|
109
|
+
*
|
|
110
|
+
* @param {import('#core/errors').RcfError} err
|
|
111
|
+
* @param {{error: (line: string) => void}} log
|
|
112
|
+
* @returns {object}
|
|
113
|
+
*/
|
|
114
|
+
export function writerErrorResult(err, log) {
|
|
115
|
+
if (err.kind === 'ioFailure') {
|
|
116
|
+
return unexpectedFailureResult(err, log);
|
|
117
|
+
}
|
|
118
|
+
if (err.kind === 'usage' && (err.rule === 'dependents' || err.rule === 'wouldOrphan')) {
|
|
119
|
+
// The writer message says "pass --cascade to opt in" - restate the
|
|
120
|
+
// remedy in this surface's vocabulary (D10: names the dependents
|
|
121
|
+
// and the cascade: true remedy).
|
|
122
|
+
const message = `${err.message.replace(/pass --cascade to opt in/, 'set cascade: true to delete dependents too')}`;
|
|
123
|
+
return errorResult([rcfError({
|
|
124
|
+
kind: 'usage',
|
|
125
|
+
message,
|
|
126
|
+
...(err.documentId !== undefined ? { documentId: err.documentId } : {}),
|
|
127
|
+
rule: err.rule,
|
|
128
|
+
})]);
|
|
129
|
+
}
|
|
130
|
+
return errorResult([err]);
|
|
131
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// MCP prompts (Phase 7 §D16-A). Serves the Phase 7.5 agent-guidance
|
|
2
|
+
// playbooks as static, argument-free prompts via prompts/list and
|
|
3
|
+
// prompts/get. Same plumbing posture as the D15-A resources: the pack
|
|
4
|
+
// owns content (guidance/manifest.json is the contract file, OQ-P7.5-1);
|
|
5
|
+
// this file reads the mapped markdown and serves bytes, byte-faithful.
|
|
6
|
+
// Semantic-adequacy assessment prompts stay Phase 7.x (spec §7).
|
|
7
|
+
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { JsonRpcError, INVALID_PARAMS } from '#core/mcp-shell';
|
|
12
|
+
import { GUIDANCE_DIR, readGuidanceManifest } from './resources.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create the prompt registry.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} [opts]
|
|
18
|
+
* @param {string} [opts.guidanceDir]
|
|
19
|
+
* @returns {{list: () => Promise<object>, get: (params: object) => Promise<object>}}
|
|
20
|
+
*/
|
|
21
|
+
export function createPromptRegistry({ guidanceDir = GUIDANCE_DIR } = {}) {
|
|
22
|
+
async function list() {
|
|
23
|
+
const manifest = await readGuidanceManifest(guidanceDir);
|
|
24
|
+
return {
|
|
25
|
+
prompts: manifest.prompts.map((p) => ({
|
|
26
|
+
name: p.name,
|
|
27
|
+
description: p.description,
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function get(params) {
|
|
33
|
+
const name = params?.name;
|
|
34
|
+
const manifest = await readGuidanceManifest(guidanceDir);
|
|
35
|
+
const entry = manifest.prompts.find((p) => p.name === name);
|
|
36
|
+
if (!entry) {
|
|
37
|
+
throw new JsonRpcError(INVALID_PARAMS, `Unknown prompt: ${name}`);
|
|
38
|
+
}
|
|
39
|
+
const text = await readFile(join(guidanceDir, entry.file), 'utf8');
|
|
40
|
+
return {
|
|
41
|
+
description: entry.description,
|
|
42
|
+
messages: [
|
|
43
|
+
{ role: 'user', content: { type: 'text', text } },
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { list, get };
|
|
49
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
// MCP resources (Phase 7 §D15 / D15-A). Four URI forms over a fresh
|
|
2
|
+
// walk per call (D14):
|
|
3
|
+
//
|
|
4
|
+
// rcf://tree - project index: {id, kind, title, filePath} per doc
|
|
5
|
+
// rcf://doc/<id> - one resource per document id, incl. inline AC / TC
|
|
6
|
+
// rcf://docs/<slug> - static methodology docs from the Phase 7.5
|
|
7
|
+
// guidance pack, wired through guidance/manifest.json
|
|
8
|
+
// (the pack owns content; this file serves bytes)
|
|
9
|
+
// rcf://guidance/<slug> - manifest-declared guidance blocks that are not
|
|
10
|
+
// themselves markdown files. Currently one:
|
|
11
|
+
// `platform-invariants` serves the
|
|
12
|
+
// `manifest.platformInvariants[]` array as JSON
|
|
13
|
+
// (Track C+D §8.2 / §13.3 AC).
|
|
14
|
+
//
|
|
15
|
+
// No subscriptions, no listChanged, no resource templates (spec §7).
|
|
16
|
+
// resources/list returns everything in one page; a cursor param is
|
|
17
|
+
// accepted and ignored. Unknown URIs are the MCP resources protocol
|
|
18
|
+
// error -32002.
|
|
19
|
+
|
|
20
|
+
import { readFile } from 'node:fs/promises';
|
|
21
|
+
import { dirname, join, resolve } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
import { JsonRpcError, RESOURCE_NOT_FOUND } from '#core/mcp-shell';
|
|
25
|
+
import { walkTree } from '#core/store';
|
|
26
|
+
import { pathForId } from '#core/store/loader.js';
|
|
27
|
+
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
/** The guidance pack ships with the server install, not the served project. */
|
|
30
|
+
export const GUIDANCE_DIR = resolve(here, '..', '..', 'guidance');
|
|
31
|
+
|
|
32
|
+
const TREE_URI = 'rcf://tree';
|
|
33
|
+
const DOC_PREFIX = 'rcf://doc/';
|
|
34
|
+
const DOCS_PREFIX = 'rcf://docs/';
|
|
35
|
+
const GUIDANCE_PREFIX = 'rcf://guidance/';
|
|
36
|
+
|
|
37
|
+
// Track C+D §8.2 / §13.3 AC: the platform-invariants block gets its own
|
|
38
|
+
// MCP resource, distinct from `rcf://docs/*` (which serves markdown
|
|
39
|
+
// files). The list is derived from the shipping manifest.json; the URI
|
|
40
|
+
// slug is stable.
|
|
41
|
+
const PLATFORM_INVARIANTS_URI = `${GUIDANCE_PREFIX}platform-invariants`;
|
|
42
|
+
const PLATFORM_INVARIANTS_SLUG = 'platform-invariants';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Read and parse guidance/manifest.json - the Phase 7.5 contract file
|
|
46
|
+
* (OQ-P7.5-1). Read per call: cheap, and the manifest is the single
|
|
47
|
+
* source of truth for what is served (pack D2 deliberately excludes
|
|
48
|
+
* README.md and manifest.json itself).
|
|
49
|
+
*
|
|
50
|
+
* @param {string} guidanceDir
|
|
51
|
+
* @returns {Promise<{docs: Array<{slug: string, file: string, title: string}>, prompts: Array<object>}>}
|
|
52
|
+
*/
|
|
53
|
+
export async function readGuidanceManifest(guidanceDir) {
|
|
54
|
+
const raw = await readFile(join(guidanceDir, 'manifest.json'), 'utf8');
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Best-effort display title for a document. Kinds carry different
|
|
60
|
+
* naming fields; inline AC / TC entries carry only a description.
|
|
61
|
+
*
|
|
62
|
+
* @param {object} doc
|
|
63
|
+
* @returns {string | null}
|
|
64
|
+
*/
|
|
65
|
+
function titleOf(doc) {
|
|
66
|
+
if (!doc || typeof doc !== 'object') return null;
|
|
67
|
+
return doc.title ?? doc.name ?? doc.productName ?? doc.description ?? null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Relative file path for a document id; inline AC / TC ids resolve to
|
|
72
|
+
* their parent's file.
|
|
73
|
+
*
|
|
74
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
75
|
+
* @param {string} id
|
|
76
|
+
* @returns {string | null}
|
|
77
|
+
*/
|
|
78
|
+
function filePathFor(tree, id) {
|
|
79
|
+
const resolved = pathForId(id);
|
|
80
|
+
if (resolved) return `rcf/${resolved.relPath}`;
|
|
81
|
+
const parentId = tree.parentByChild.get(id);
|
|
82
|
+
if (!parentId) return null;
|
|
83
|
+
const parentResolved = pathForId(parentId);
|
|
84
|
+
return parentResolved ? `rcf/${parentResolved.relPath}` : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Enumerate every document id in the tree: standalone docs (byId) plus
|
|
89
|
+
* inline AC / TC ids (parentByChild keys not present in byId).
|
|
90
|
+
*
|
|
91
|
+
* @param {import('#core/store/walker.js').TreeModel} tree
|
|
92
|
+
* @returns {Array<{id: string, kind: string, doc: object | null}>}
|
|
93
|
+
*/
|
|
94
|
+
function enumerateDocuments(tree) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (const [id, doc] of tree.byId.entries()) {
|
|
97
|
+
out.push({ id, kind: tree.kindById.get(id) ?? 'unknown', doc });
|
|
98
|
+
}
|
|
99
|
+
for (const id of tree.parentByChild.keys()) {
|
|
100
|
+
if (tree.byId.has(id)) continue;
|
|
101
|
+
const kind = id.startsWith('AC-') ? 'ac' : id.startsWith('TC-') ? 'tc' : 'unknown';
|
|
102
|
+
out.push({ id, kind, doc: resolveInlineDoc(tree, id) });
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveInlineDoc(tree, id) {
|
|
108
|
+
const parentId = tree.parentByChild.get(id);
|
|
109
|
+
if (!parentId) return null;
|
|
110
|
+
const parent = tree.byId.get(parentId);
|
|
111
|
+
if (!parent) return null;
|
|
112
|
+
const entries = id.startsWith('AC-') ? parent.acceptanceCriteria : parent.testCases;
|
|
113
|
+
return (entries ?? []).find((e) => e.id === id) ?? null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create the resource registry bound to one project root.
|
|
118
|
+
*
|
|
119
|
+
* @param {object} opts
|
|
120
|
+
* @param {string} opts.projectRoot
|
|
121
|
+
* @param {string} [opts.guidanceDir]
|
|
122
|
+
* @returns {{list: () => Promise<object>, read: (uri: string) => Promise<object>}}
|
|
123
|
+
*/
|
|
124
|
+
export function createResourceRegistry({ projectRoot, guidanceDir = GUIDANCE_DIR }) {
|
|
125
|
+
async function buildTreeIndex() {
|
|
126
|
+
const { tree } = await walkTree({ projectRoot });
|
|
127
|
+
return {
|
|
128
|
+
tree,
|
|
129
|
+
index: {
|
|
130
|
+
project: tree.manifest?.projectName ?? null,
|
|
131
|
+
documents: enumerateDocuments(tree).map(({ id, kind, doc }) => ({
|
|
132
|
+
id,
|
|
133
|
+
kind,
|
|
134
|
+
title: titleOf(doc),
|
|
135
|
+
filePath: filePathFor(tree, id),
|
|
136
|
+
})),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function list() {
|
|
142
|
+
// A broken tree does not block resources: the walker tolerates
|
|
143
|
+
// per-document failures and this surface serves what loaded -
|
|
144
|
+
// rcf_validate is the diagnosis channel (D18 posture).
|
|
145
|
+
const { tree, index } = await buildTreeIndex();
|
|
146
|
+
const manifest = await readGuidanceManifest(guidanceDir);
|
|
147
|
+
const resources = [
|
|
148
|
+
{
|
|
149
|
+
uri: TREE_URI,
|
|
150
|
+
name: 'tree',
|
|
151
|
+
title: index.project ? `${index.project} document index` : 'RCF document index',
|
|
152
|
+
description: 'Project document index: id, kind, title and file path per document, from a fresh tree walk.',
|
|
153
|
+
mimeType: 'application/json',
|
|
154
|
+
},
|
|
155
|
+
...enumerateDocuments(tree).map(({ id, kind, doc }) => ({
|
|
156
|
+
uri: `${DOC_PREFIX}${id}`,
|
|
157
|
+
name: id,
|
|
158
|
+
...(titleOf(doc) ? { title: `${id}: ${titleOf(doc)}` } : {}),
|
|
159
|
+
description: `RCF ${kind} document ${id}`,
|
|
160
|
+
mimeType: 'application/json',
|
|
161
|
+
})),
|
|
162
|
+
...manifest.docs.map((d) => ({
|
|
163
|
+
uri: `${DOCS_PREFIX}${d.slug}`,
|
|
164
|
+
name: d.slug,
|
|
165
|
+
title: d.title,
|
|
166
|
+
description: `RCF methodology: ${d.title}. Canonical web reference: https://stravica.ai/rcf-methodology/`,
|
|
167
|
+
mimeType: 'text/markdown',
|
|
168
|
+
})),
|
|
169
|
+
...(Array.isArray(manifest.platformInvariants) && manifest.platformInvariants.length > 0
|
|
170
|
+
? [{
|
|
171
|
+
uri: PLATFORM_INVARIANTS_URI,
|
|
172
|
+
name: PLATFORM_INVARIANTS_SLUG,
|
|
173
|
+
title: 'RCF platform invariants',
|
|
174
|
+
description: 'Platform-wide invariants (currently: never-skip-RCF). JSON array of {id, title, text} records lifted verbatim from guidance/manifest.json.',
|
|
175
|
+
mimeType: 'application/json',
|
|
176
|
+
}]
|
|
177
|
+
: []),
|
|
178
|
+
];
|
|
179
|
+
return { resources };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function read(uri) {
|
|
183
|
+
if (typeof uri !== 'string' || uri.length === 0) {
|
|
184
|
+
throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
|
|
185
|
+
}
|
|
186
|
+
if (uri === TREE_URI) {
|
|
187
|
+
const { index } = await buildTreeIndex();
|
|
188
|
+
return {
|
|
189
|
+
contents: [{
|
|
190
|
+
uri,
|
|
191
|
+
mimeType: 'application/json',
|
|
192
|
+
text: JSON.stringify(index, null, 2),
|
|
193
|
+
}],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
if (uri.startsWith(DOCS_PREFIX)) {
|
|
197
|
+
const slug = uri.slice(DOCS_PREFIX.length);
|
|
198
|
+
const manifest = await readGuidanceManifest(guidanceDir);
|
|
199
|
+
const entry = manifest.docs.find((d) => d.slug === slug);
|
|
200
|
+
if (!entry) {
|
|
201
|
+
throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
|
|
202
|
+
}
|
|
203
|
+
const text = await readFile(join(guidanceDir, entry.file), 'utf8');
|
|
204
|
+
return { contents: [{ uri, mimeType: 'text/markdown', text }] };
|
|
205
|
+
}
|
|
206
|
+
if (uri.startsWith(GUIDANCE_PREFIX)) {
|
|
207
|
+
const slug = uri.slice(GUIDANCE_PREFIX.length);
|
|
208
|
+
if (slug !== PLATFORM_INVARIANTS_SLUG) {
|
|
209
|
+
throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
|
|
210
|
+
}
|
|
211
|
+
const manifest = await readGuidanceManifest(guidanceDir);
|
|
212
|
+
const invariants = Array.isArray(manifest.platformInvariants) ? manifest.platformInvariants : [];
|
|
213
|
+
// Serves the manifest bytes verbatim - the never-skip-RCF locking
|
|
214
|
+
// test in test/guidance/ owns the byte-for-byte canonicalness of
|
|
215
|
+
// each entry's text; this handler is the transport, not a second
|
|
216
|
+
// source of truth.
|
|
217
|
+
return {
|
|
218
|
+
contents: [{
|
|
219
|
+
uri,
|
|
220
|
+
mimeType: 'application/json',
|
|
221
|
+
text: JSON.stringify(invariants, null, 2),
|
|
222
|
+
}],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (uri.startsWith(DOC_PREFIX)) {
|
|
226
|
+
const id = uri.slice(DOC_PREFIX.length);
|
|
227
|
+
const { tree } = await walkTree({ projectRoot });
|
|
228
|
+
const doc = tree.byId.get(id) ?? resolveInlineDoc(tree, id);
|
|
229
|
+
if (!doc) {
|
|
230
|
+
throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
contents: [{
|
|
234
|
+
uri,
|
|
235
|
+
mimeType: 'application/json',
|
|
236
|
+
text: JSON.stringify(doc, null, 2),
|
|
237
|
+
}],
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
throw new JsonRpcError(RESOURCE_NOT_FOUND, 'Resource not found', { uri });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return { list, read };
|
|
244
|
+
}
|