rcf-lite 0.8.0 → 0.10.0
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 +124 -51
- package/README.md +8 -4
- package/bin/rcf.js +147 -53
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/README.md +1 -1
- package/guidance/build-cycle-playbook.md +51 -51
- package/guidance/build-cycle.md +7 -7
- package/guidance/document-model.md +1 -1
- package/guidance/elicitation-playbook.md +29 -29
- package/guidance/harness-template.md +21 -10
- package/guidance/managed/README.md +1 -1
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +20 -9
- package/guidance/manifest.json +1 -1
- package/guidance/overview.md +4 -4
- package/package.json +5 -7
- package/rcf/adrs/adr-008.json +1 -1
- package/rcf/adrs/adr-009.json +4 -4
- package/rcf/adrs/adr-010.json +30 -0
- package/rcf/code-nodes/cn-016.json +1 -1
- package/rcf/code-nodes/cn-019.json +1 -1
- package/rcf/code-nodes/cn-020.json +1 -1
- package/rcf/code-nodes/cn-021.json +1 -1
- package/rcf/code-nodes/cn-022.json +1 -1
- package/rcf/code-nodes/cn-049.json +1 -1
- package/rcf/code-nodes/cn-055.json +1 -1
- package/rcf/code-nodes/cn-057.json +5 -5
- package/rcf/code-nodes/cn-058.json +18 -0
- package/rcf/code-nodes/cn-059.json +14 -0
- package/rcf/code-nodes/cn-060.json +14 -0
- package/rcf/code-nodes/cn-061.json +14 -0
- package/rcf/code-nodes/cn-062.json +14 -0
- package/rcf/code-nodes/cn-063.json +15 -0
- package/rcf/code-nodes/cn-064.json +15 -0
- package/rcf/code-nodes/cn-065.json +16 -0
- package/rcf/code-nodes/cn-066.json +14 -0
- package/rcf/code-nodes/cn-067.json +15 -0
- package/rcf/code-nodes/cn-068.json +15 -0
- package/rcf/code-nodes/cn-069.json +16 -0
- package/rcf/fbs/fbs-005.json +1 -1
- package/rcf/fbs/fbs-006.json +2 -2
- package/rcf/fbs/fbs-007.json +1 -1
- package/rcf/fbs/fbs-014.json +1 -1
- package/rcf/fbs/fbs-015.json +9 -8
- package/rcf/fbs/fbs-016.json +39 -0
- package/rcf/fbs/fbs-017.json +40 -0
- package/rcf/fbs/fbs-018.json +34 -0
- package/rcf/fbs/fbs-019.json +33 -0
- package/rcf/requirements/req-008.json +1 -1
- package/rcf/requirements/req-009.json +2 -2
- package/rcf/requirements/req-010.json +20 -0
- package/rcf/test-suites/PENDING.md +2 -2
- package/rcf/test-suites/ts-004.json +1 -1
- package/rcf/test-suites/ts-006.json +3 -3
- package/rcf/test-suites/ts-008.json +2 -2
- package/rcf/test-suites/ts-009.json +2 -2
- package/rcf/test-suites/ts-017.json +1 -1
- package/rcf/test-suites/ts-024.json +1 -1
- package/rcf/test-suites/ts-025.json +32 -18
- package/rcf/test-suites/ts-026.json +54 -0
- package/rcf/test-suites/ts-027.json +115 -0
- package/rcf/test-suites/ts-028.json +46 -0
- package/rcf/test-suites/ts-029.json +46 -0
- package/rcf/user-stories/us-1001.json +56 -0
- package/rcf/user-stories/us-1002.json +96 -0
- package/rcf/user-stories/us-1003.json +48 -0
- package/rcf/user-stories/us-1004.json +48 -0
- package/rcf/user-stories/us-805.json +2 -2
- package/rcf/user-stories/us-901.json +13 -13
- package/src/blueprint/apply.js +464 -0
- package/src/blueprint/conflicts.js +351 -0
- package/src/blueprint/diff.js +82 -0
- package/src/blueprint/index.js +12 -0
- package/src/blueprint/list.js +21 -0
- package/src/blueprint/loader.js +163 -0
- package/src/blueprint/manifest-writer.js +49 -0
- package/src/blueprint/namespace.js +145 -0
- package/src/blueprint/remove.js +105 -0
- package/src/blueprint/resolutions.js +83 -0
- package/src/blueprint/standards.js +148 -0
- package/src/blueprint/supersede.js +318 -0
- package/src/browser-verify/invariants.js +33 -6
- package/src/build/bundle.js +37 -14
- package/src/build/formatters/markdown.js +9 -9
- package/src/build/mark.js +3 -3
- package/src/build/queue.js +1 -1
- package/src/build/standards-selector.js +52 -0
- package/src/cli/blueprint.js +325 -0
- package/src/cli/browser-verify.js +1 -1
- package/src/cli/build.js +139 -69
- package/src/cli/coverage.js +1 -1
- package/src/cli/create.js +48 -3
- package/src/cli/delete.js +2 -2
- package/src/cli/design.js +10 -10
- package/src/cli/fbs.js +1 -1
- package/src/cli/finalise.js +22 -20
- package/src/cli/help.js +269 -89
- package/src/cli/impact.js +1 -1
- package/src/cli/init.js +20 -5
- package/src/cli/intake.js +3 -3
- package/src/cli/link.js +3 -3
- package/src/cli/preflight.js +2 -2
- package/src/cli/read.js +1 -1
- package/src/cli/req-baseline.js +2 -2
- package/src/cli/req-classify.js +3 -3
- package/src/cli/review.js +1 -1
- package/src/cli/standards.js +127 -0
- package/src/cli/test-suite.js +1 -1
- package/src/cli/trace.js +1 -1
- package/src/cli/ui-baseline.js +3 -3
- package/src/cli/ui-classify.js +4 -4
- package/src/cli/update.js +2 -2
- package/src/cli/validate.js +2 -2
- package/src/cli/view.js +12 -10
- package/src/core/store/ids.js +168 -18
- package/src/core/store/loader.js +27 -16
- package/src/core/store/walker.js +27 -15
- package/src/core/store/writer.js +1 -1
- package/src/deployment/index.js +13 -0
- package/src/deployment/placeholder-detector.js +113 -0
- package/src/design/writer.js +3 -3
- package/src/finalise/detect.js +32 -38
- package/src/finalise/index.js +0 -1
- package/src/finalise/install.js +9 -8
- package/src/finalise/spawn.js +14 -10
- package/src/mcp/tools.js +1 -1
- package/src/query/formatters/table.js +7 -10
- package/src/query/trace.js +45 -4
- package/src/req-baseline/gate.js +1 -1
- package/src/ui-baseline/manifest-writer.js +2 -2
- package/src/verify/cli/cleanup.js +1 -1
- package/src/verify/cli/mcp.js +1 -1
- package/src/verify/cli/provision.js +1 -1
- package/src/verify/cli/report.js +1 -1
- package/src/verify/cli/run.js +1 -1
- package/src/view-supervisor/manifest-writer.js +2 -2
- package/bin/rcf-verify.js +0 -122
- package/src/verify/cli/help.js +0 -56
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// `rcf standards <verb>` — Phase 1 landing (add | list).
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from 'node:util';
|
|
4
|
+
|
|
5
|
+
import { isRcfError } from '#core/errors';
|
|
6
|
+
import { walkTree } from '#core/store';
|
|
7
|
+
import { findProjectRoot } from '../view/index.js';
|
|
8
|
+
import { listStandards, registerStandardsPack } from '../blueprint/index.js';
|
|
9
|
+
|
|
10
|
+
export const HELP = `Usage: rcf define standards <verb> [options]
|
|
11
|
+
|
|
12
|
+
Verbs:
|
|
13
|
+
add <source> Register a standards pack against the project.
|
|
14
|
+
Reference-by-default: if <source> lives inside
|
|
15
|
+
the project root, the pack is referenced in place
|
|
16
|
+
and no copy is written. If <source> lives OUTSIDE
|
|
17
|
+
the project root, the pack is copied into
|
|
18
|
+
rcf/standards/<slug>/ so the tree stays portable.
|
|
19
|
+
list List every registered standards pack.
|
|
20
|
+
|
|
21
|
+
Options (for add):
|
|
22
|
+
--slug <slug> Required. Kebab slug for this pack.
|
|
23
|
+
--tags <t1,t2,...> Required. Comma-separated tag vocabulary.
|
|
24
|
+
--summary <string> Optional short summary read by the selective-
|
|
25
|
+
retrieval step alongside the tags.
|
|
26
|
+
--tests-provided-by <val> Required. One of: standard | agent | none.
|
|
27
|
+
--provenance <val> Required. One of: personal | corporate.
|
|
28
|
+
--dry-run Print intended writes without executing.
|
|
29
|
+
--quiet Suppress non-error stdout.
|
|
30
|
+
--help Print this help.
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const OPTION_SPEC = {
|
|
34
|
+
slug: { type: 'string' },
|
|
35
|
+
tags: { type: 'string' },
|
|
36
|
+
summary: { type: 'string' },
|
|
37
|
+
'tests-provided-by': { type: 'string' },
|
|
38
|
+
provenance: { type: 'string' },
|
|
39
|
+
'dry-run': { type: 'boolean' },
|
|
40
|
+
quiet: { type: 'boolean' },
|
|
41
|
+
help: { type: 'boolean' },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {string[]} argv
|
|
46
|
+
* @param {object} [deps]
|
|
47
|
+
* @returns {Promise<number>}
|
|
48
|
+
*/
|
|
49
|
+
export async function main(argv, deps = {}) {
|
|
50
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
51
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
52
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
53
|
+
|
|
54
|
+
let parsed;
|
|
55
|
+
try {
|
|
56
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: true, strict: true });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
stderr.write(`[error] ${err.message}\n`);
|
|
59
|
+
stderr.write(HELP);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
if (parsed.values.help || parsed.positionals.length === 0) {
|
|
63
|
+
stdout.write(HELP);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
const verb = parsed.positionals[0];
|
|
67
|
+
const rest = parsed.positionals.slice(1);
|
|
68
|
+
|
|
69
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
70
|
+
if (!projectRoot) {
|
|
71
|
+
stderr.write('[error] no rcf/ tree found in this directory or any ancestor.\n');
|
|
72
|
+
return 2;
|
|
73
|
+
}
|
|
74
|
+
const { tree, errors } = await walkTree({ projectRoot });
|
|
75
|
+
if (errors.length > 0 && verb !== 'list') {
|
|
76
|
+
for (const e of errors) stderr.write(`[tree] ${e.kind}: ${e.message}\n`);
|
|
77
|
+
return 2;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (verb === 'add') {
|
|
81
|
+
if (rest.length === 0) {
|
|
82
|
+
stderr.write('[error] standards add: missing <source>\n');
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
if (!parsed.values.slug || !parsed.values.tags || !parsed.values['tests-provided-by'] || !parsed.values.provenance) {
|
|
86
|
+
stderr.write('[error] standards add: --slug, --tags, --tests-provided-by and --provenance are required\n');
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
const result = await registerStandardsPack({
|
|
90
|
+
projectRoot, tree,
|
|
91
|
+
sourcePath: rest[0],
|
|
92
|
+
slug: parsed.values.slug,
|
|
93
|
+
tags: parsed.values.tags.split(',').map((t) => t.trim()).filter(Boolean),
|
|
94
|
+
summary: parsed.values.summary,
|
|
95
|
+
testsProvidedBy: parsed.values['tests-provided-by'],
|
|
96
|
+
provenance: parsed.values.provenance,
|
|
97
|
+
dryRun: parsed.values['dry-run'] === true,
|
|
98
|
+
});
|
|
99
|
+
if (isRcfError(result)) {
|
|
100
|
+
stderr.write(`[error] standards add: ${result.message}\n`);
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
if (!parsed.values.quiet) {
|
|
104
|
+
const shape = result.copyPath ? `copied to ${result.copyPath}` : `referenced in place at ${result.entry.sourcePath}`;
|
|
105
|
+
const state = result.alreadyRegistered ? 'already registered (no change)' : 'registered';
|
|
106
|
+
stdout.write(`[standards] '${result.entry.slug}' ${state} (${shape}).\n`);
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (verb === 'list') {
|
|
112
|
+
const rows = listStandards(tree);
|
|
113
|
+
if (rows.length === 0) {
|
|
114
|
+
if (!parsed.values.quiet) stdout.write('[standards] no standards packs registered on this project.\n');
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
const shape = row.copyPath ? 'copied' : 'referenced';
|
|
119
|
+
stdout.write(`${row.slug}\t${row.testsProvidedBy}\t${row.provenance}\t${shape}\t${(row.tags ?? []).join(',')}\n`);
|
|
120
|
+
}
|
|
121
|
+
return 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
stderr.write(`[error] standards: unknown verb '${verb}'\n`);
|
|
125
|
+
stderr.write(HELP);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
package/src/cli/test-suite.js
CHANGED
|
@@ -34,7 +34,7 @@ const APPROVE_OPTION_SPEC = {
|
|
|
34
34
|
|
|
35
35
|
const VALID_PROFILES = new Set(['mock', 'stub', 'fixture', 'live', 'mixed']);
|
|
36
36
|
|
|
37
|
-
export const HELP = `Usage: rcf test-suite <ts-id> <verb> [options]
|
|
37
|
+
export const HELP = `Usage: rcf build test-suite <ts-id> <verb> [options]
|
|
38
38
|
|
|
39
39
|
Verbs:
|
|
40
40
|
provenance Author runtimeProvenance on the TS or a specific TC
|
package/src/cli/trace.js
CHANGED
|
@@ -29,7 +29,7 @@ const OPTION_SPEC = {
|
|
|
29
29
|
'to-code': { type: 'boolean' },
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
export const HELP = `Usage: rcf trace <id|path> [options]
|
|
32
|
+
export const HELP = `Usage: rcf audit trace <id|path> [options]
|
|
33
33
|
|
|
34
34
|
Walk the graph from <id> forward (descendants), backward (ancestors),
|
|
35
35
|
or both. Default is --forward. When <id> is not a known document id, it
|
package/src/cli/ui-baseline.js
CHANGED
|
@@ -48,7 +48,7 @@ const OPTION_SPEC = {
|
|
|
48
48
|
help: { type: 'boolean' },
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export const HELP = `Usage: rcf ui-baseline <verb> [options]
|
|
51
|
+
export const HELP = `Usage: rcf discover ui-baseline <verb> [options]
|
|
52
52
|
|
|
53
53
|
Manage the project's ruled UI defaults (theme, layout, contrast,
|
|
54
54
|
components, auth flow). Written once per project as a uiBaseline record
|
|
@@ -136,7 +136,7 @@ export async function main(argv, deps = {}) {
|
|
|
136
136
|
function runShow({ tree, stdout, flags }) {
|
|
137
137
|
const record = tree.manifest?.uiBaseline;
|
|
138
138
|
if (!record) {
|
|
139
|
-
stdout.write('ui-baseline: no baseline recorded. Run \'rcf ui-baseline init\'.\n');
|
|
139
|
+
stdout.write('ui-baseline: no baseline recorded. Run \'rcf discover ui-baseline init\'.\n');
|
|
140
140
|
return 0;
|
|
141
141
|
}
|
|
142
142
|
if (flags.json) {
|
|
@@ -171,7 +171,7 @@ async function runOptOut({ tree, projectRoot, stdout, stderr, flags, now }) {
|
|
|
171
171
|
}
|
|
172
172
|
const normalisedField = flags.field.startsWith('defaults.') ? flags.field.slice('defaults.'.length) : flags.field;
|
|
173
173
|
if (!isKnownBaselinePath(normalisedField)) {
|
|
174
|
-
stderr.write(`[error] usage ui-baseline opt-out: unknown baseline field '${normalisedField}' (see 'rcf ui-baseline show' for the field list)\n`);
|
|
174
|
+
stderr.write(`[error] usage ui-baseline opt-out: unknown baseline field '${normalisedField}' (see 'rcf discover ui-baseline show' for the field list)\n`);
|
|
175
175
|
return 2;
|
|
176
176
|
}
|
|
177
177
|
const result = await writeUiBaselineOptOut({
|
package/src/cli/ui-classify.js
CHANGED
|
@@ -19,11 +19,11 @@ const OPTION_SPEC = {
|
|
|
19
19
|
help: { type: 'boolean' },
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export const HELP = `Usage: rcf ui-classify <fbs-id> [options]
|
|
22
|
+
export const HELP = `Usage: rcf discover ui-classify <fbs-id> [options]
|
|
23
23
|
|
|
24
24
|
Run the UI-bearing classifier on one FBS and print the verdict. Does
|
|
25
25
|
not write to the FBS document; ratify with:
|
|
26
|
-
rcf update <fbs-id> --set uiBearing=true
|
|
26
|
+
rcf define update <fbs-id> --set uiBearing=true
|
|
27
27
|
|
|
28
28
|
Options:
|
|
29
29
|
--json Emit the uiClassification block as JSON.
|
|
@@ -98,9 +98,9 @@ export async function main(argv, deps = {}) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
if (block.verdict === 'ui') {
|
|
101
|
-
stdout.write(' Ratify with: rcf update ' + fbsId + ' --set uiBearing=true\n');
|
|
101
|
+
stdout.write(' Ratify with: rcf define update ' + fbsId + ' --set uiBearing=true\n');
|
|
102
102
|
} else if (block.verdict === 'notUi' && signals.length === 0) {
|
|
103
|
-
stdout.write(' No UI signals detected. Override with: rcf update ' + fbsId + ' --set uiBearing=true\n');
|
|
103
|
+
stdout.write(' No UI signals detected. Override with: rcf define update ' + fbsId + ' --set uiBearing=true\n');
|
|
104
104
|
} else if (block.verdict === 'operatorOverride') {
|
|
105
105
|
stdout.write(' Operator ruling recorded on FBS.uiBearing wins over the classifier.\n');
|
|
106
106
|
}
|
package/src/cli/update.js
CHANGED
|
@@ -26,7 +26,7 @@ const OPTION_SPEC = {
|
|
|
26
26
|
'derive-deps': { type: 'boolean' },
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export const HELP = `Usage: rcf update <id> [options]
|
|
29
|
+
export const HELP = `Usage: rcf define update <id> [options]
|
|
30
30
|
|
|
31
31
|
Options:
|
|
32
32
|
--set <dotPath>=<value> Set a field; repeatable
|
|
@@ -80,7 +80,7 @@ export async function main(argv, deps = {}) {
|
|
|
80
80
|
// update is gated on the POST-write tree state inside the writer, so
|
|
81
81
|
// repairing a broken doc is exactly what this verb is now for.
|
|
82
82
|
if (walkResult.errors.length > 0) {
|
|
83
|
-
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf validate' for details)\n`);
|
|
83
|
+
stderr.write(`[warn] tree has ${walkResult.errors.length} pre-existing issue(s); proceeding - writes are validated against the post-write state (run 'rcf define validate' for details)\n`);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const sets = [];
|
package/src/cli/validate.js
CHANGED
|
@@ -18,7 +18,7 @@ const OPTION_SPEC = {
|
|
|
18
18
|
'no-code': { type: 'boolean' },
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
export const HELP = `Usage: rcf validate [options]
|
|
21
|
+
export const HELP = `Usage: rcf define validate [options]
|
|
22
22
|
|
|
23
23
|
Walk the rcf/ tree and report schema-validation and broken-reference
|
|
24
24
|
issues. Exits 0 when clean, 3 on issues.
|
|
@@ -140,7 +140,7 @@ export async function main(argv, deps = {}) {
|
|
|
140
140
|
}
|
|
141
141
|
if (errors.length === 0) {
|
|
142
142
|
if (!flags.quiet) {
|
|
143
|
-
stdout.write('rcf validate: tree is clean.\n');
|
|
143
|
+
stdout.write('rcf define validate: tree is clean.\n');
|
|
144
144
|
// B4 (E2E matrix 2026-07-06-003): scaffold TODO placeholders were
|
|
145
145
|
// surviving to otherwise-valid trees unflagged. Non-blocking notice
|
|
146
146
|
// only - the exit code is unchanged.
|
package/src/cli/view.js
CHANGED
|
@@ -24,7 +24,7 @@ import { parsePersistUntil } from '../view-supervisor/persist-until.js';
|
|
|
24
24
|
export const DEFAULT_PORT = 4373;
|
|
25
25
|
export const SHUTDOWN_BUDGET_MS = 2000;
|
|
26
26
|
|
|
27
|
-
export const HELP = `Usage: rcf view [subverb] [options]
|
|
27
|
+
export const HELP = `Usage: rcf audit view [subverb] [options]
|
|
28
28
|
|
|
29
29
|
Serve the on-disk RCF tree as a live HTML review surface. Runs a
|
|
30
30
|
long-running HTTP + SSE server on 127.0.0.1 that watches rcf/ and pushes
|
|
@@ -32,10 +32,10 @@ tree updates to the connected browser tab. No on-disk output; no static
|
|
|
32
32
|
files are written.
|
|
33
33
|
|
|
34
34
|
Subverbs (spec §9.2):
|
|
35
|
-
rcf view
|
|
35
|
+
rcf audit view Foreground server (default; lifetime tied to
|
|
36
36
|
the invoking session). Backward-compatible
|
|
37
37
|
with pre-0.7.0 callers.
|
|
38
|
-
rcf view start [--detach|--foreground] [--persist-until <duration|iso>]
|
|
38
|
+
rcf audit view start [--detach|--foreground] [--persist-until <duration|iso>]
|
|
39
39
|
Start the server. --detach forks a supervised
|
|
40
40
|
background process that persists across the
|
|
41
41
|
parent session's death; the manifest carries
|
|
@@ -50,12 +50,14 @@ Subverbs (spec §9.2):
|
|
|
50
50
|
--detach; non-interactive callers keep
|
|
51
51
|
--foreground so a script does not orphan a
|
|
52
52
|
process.
|
|
53
|
-
rcf view status [--json]
|
|
53
|
+
rcf audit view status [--json]
|
|
54
|
+
Print the supervisor state:
|
|
54
55
|
running | stale | not-started.
|
|
55
|
-
rcf view stop
|
|
56
|
+
rcf audit view stop Send SIGTERM to the supervised process, wait
|
|
56
57
|
for a clean shutdown, and clear the manifest
|
|
57
58
|
record.
|
|
58
|
-
rcf view logs [--tail <n>]
|
|
59
|
+
rcf audit view logs [--tail <n>]
|
|
60
|
+
Print the supervisor log tail (default 200).
|
|
59
61
|
|
|
60
62
|
Options:
|
|
61
63
|
--port <n> Bind the HTTP server on the given port.
|
|
@@ -83,7 +85,7 @@ Security posture:
|
|
|
83
85
|
Shutdown:
|
|
84
86
|
Ctrl-C (SIGINT) or SIGTERM triggers a clean shutdown: watcher
|
|
85
87
|
closed, SSE connections drained with a shutdown event, port
|
|
86
|
-
released, 2s force-exit budget. Detached: rcf view stop sends
|
|
88
|
+
released, 2s force-exit budget. Detached: rcf audit view stop sends
|
|
87
89
|
SIGTERM to the supervisor and waits for a clean unwind.
|
|
88
90
|
|
|
89
91
|
Exit codes:
|
|
@@ -213,7 +215,7 @@ export function maybeAutoOpen({ target, noOpen, stream, env, stderr, spawnFn = s
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
/**
|
|
216
|
-
* Main entry for the `rcf view` subcommand. Mirrors the shape of the
|
|
218
|
+
* Main entry for the `rcf audit view` subcommand. Mirrors the shape of the
|
|
217
219
|
* old bin/rcf-view.js main().
|
|
218
220
|
*
|
|
219
221
|
* @param {string[]} argv - the argv slice *after* the "view" positional
|
|
@@ -278,7 +280,7 @@ export async function main(argv, deps = {}) {
|
|
|
278
280
|
});
|
|
279
281
|
} catch (err) {
|
|
280
282
|
if (/** @type {NodeJS.ErrnoException} */ (err).code === 'EADDRINUSE') {
|
|
281
|
-
stderr.write(`[error] usage port ${resolvedPort} is in use (another rcf view process, or a different service).\n`);
|
|
283
|
+
stderr.write(`[error] usage port ${resolvedPort} is in use (another rcf audit view process, or a different service).\n`);
|
|
282
284
|
stderr.write('Pass --port <n> or set RCF_VIEW_PORT to pick a free port.\n');
|
|
283
285
|
return 2;
|
|
284
286
|
}
|
|
@@ -286,7 +288,7 @@ export async function main(argv, deps = {}) {
|
|
|
286
288
|
return 1;
|
|
287
289
|
}
|
|
288
290
|
|
|
289
|
-
stdout.write(`rcf view server listening at ${server.url}\n`);
|
|
291
|
+
stdout.write(`rcf audit view server listening at ${server.url}\n`);
|
|
290
292
|
stdout.write('watching rcf/ - Ctrl-C to shut down\n');
|
|
291
293
|
|
|
292
294
|
maybeAutoOpen({
|
package/src/core/store/ids.js
CHANGED
|
@@ -1,25 +1,175 @@
|
|
|
1
|
-
// Id normalisation.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// Id normalisation + family grammar. This module owns THE grammar for
|
|
2
|
+
// RCF document ids: schema-family membership, filename-stem -> canonical-id
|
|
3
|
+
// inversion, and family -> tree location. The walker (walker.js), the
|
|
4
|
+
// document loader (loader.js) and the blueprint namespace helpers
|
|
5
|
+
// (src/blueprint/namespace.js) all consume the primitives defined here so
|
|
6
|
+
// the regexes live in exactly one place.
|
|
7
7
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// allocator. Both sides MUST agree: detection that normalises while
|
|
11
|
-
// allocation does not just moves the collision one step later.
|
|
8
|
+
// Two schema families, per @stravica-ai/rcf-schemas 0.4.4 (see
|
|
9
|
+
// docs/id-conventions.md):
|
|
12
10
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// - Prefix families (REQ, US, PRD, BS, TAD, TS) admit an optional
|
|
12
|
+
// lowercase kebab-slug PREFIX joined by `-` to the family prefix.
|
|
13
|
+
// `REQ-001` under blueprint `spa` becomes `spa-REQ-001`.
|
|
14
|
+
// Schema pattern (common.reqId etc.):
|
|
15
|
+
// ^([a-z][a-z0-9]*(?:-[a-z0-9]+)*-)?<PREFIX>-\d{3,}$
|
|
15
16
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// - Suffix families (ADR, TAC, FBS, CN) admit an optional lowercase
|
|
18
|
+
// kebab-slug SUFFIX joined by `-` to the numeric tail. `ADR-005`
|
|
19
|
+
// under blueprint `spa` becomes `ADR-005-spa`.
|
|
20
|
+
// Schema pattern (common.adrId etc.):
|
|
21
|
+
// ^<PREFIX>-\d{3,}(-[a-z0-9]+(?:-[a-z0-9]+)*)?$
|
|
20
22
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
+
// - Unnamespaced families (AC, TC) live inline under their parent
|
|
24
|
+
// documents and never appear as top-level files under rcf/.
|
|
25
|
+
//
|
|
26
|
+
// The RCF id patterns admit a variable-width numeric run
|
|
27
|
+
// (`^REQ-\d{3,}$`, `^AC-\d{3,}(-\d+)?$`, ...), so `REQ-001` and
|
|
28
|
+
// `REQ-0001` are BOTH legal and BOTH name requirement number 1.
|
|
29
|
+
// `normaliseId` / `sameId` collapse those spellings for the walker's
|
|
30
|
+
// `globallyUniqueIds` rule and the writer's id allocator; that identity
|
|
31
|
+
// pass runs independently of the family grammar above.
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Family membership (the single source for downstream re-exports)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export const PREFIX_FAMILIES = Object.freeze(['REQ', 'US', 'PRD', 'BS', 'TAD', 'TS']);
|
|
38
|
+
export const SUFFIX_FAMILIES = Object.freeze(['ADR', 'TAC', 'FBS', 'CN']);
|
|
39
|
+
export const UNNAMESPACED_FAMILIES = Object.freeze(['AC', 'TC']);
|
|
40
|
+
|
|
41
|
+
export const PREFIX_FAMILY_SET = new Set(PREFIX_FAMILIES);
|
|
42
|
+
export const SUFFIX_FAMILY_SET = new Set(SUFFIX_FAMILIES);
|
|
43
|
+
export const UNNAMESPACED_FAMILY_SET = new Set(UNNAMESPACED_FAMILIES);
|
|
44
|
+
|
|
45
|
+
export const SLUG_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
46
|
+
|
|
47
|
+
// Precompiled per-family regexes. Prefix families are anchored so the
|
|
48
|
+
// optional slug prefix is captured cleanly; suffix families capture the
|
|
49
|
+
// optional slug suffix. Suffix families are consulted first because they
|
|
50
|
+
// never carry a leading slug -- see parseIdParts.
|
|
51
|
+
const PREFIX_REGEXES = new Map();
|
|
52
|
+
for (const family of PREFIX_FAMILIES) {
|
|
53
|
+
PREFIX_REGEXES.set(family, new RegExp(`^(?:([a-z][a-z0-9]*(?:-[a-z0-9]+)*)-)?${family}-(\\d{3,})$`));
|
|
54
|
+
}
|
|
55
|
+
const SUFFIX_REGEXES = new Map();
|
|
56
|
+
for (const family of SUFFIX_FAMILIES) {
|
|
57
|
+
SUFFIX_REGEXES.set(family, new RegExp(`^${family}-(\\d{3,})(?:-([a-z0-9]+(?:-[a-z0-9]+)*))?$`));
|
|
58
|
+
}
|
|
59
|
+
const UNNAMESPACED_REGEXES = new Map([
|
|
60
|
+
['AC', /^AC-(\d{3,})(?:-(\d+))?$/],
|
|
61
|
+
['TC', /^TC-(\d{3,})-([a-z0-9-]+)$/],
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
// Lowercase counterparts for stem-to-canonical inversion. Filenames on
|
|
65
|
+
// disk are lower-case kebab (blueprint apply -> destPathFor -> toLowerCase),
|
|
66
|
+
// and the same lowercase pattern is what the CLI init writer produces.
|
|
67
|
+
const PREFIX_STEM_REGEXES = new Map();
|
|
68
|
+
for (const family of PREFIX_FAMILIES) {
|
|
69
|
+
PREFIX_STEM_REGEXES.set(family, new RegExp(`^(?:([a-z][a-z0-9]*(?:-[a-z0-9]+)*)-)?${family.toLowerCase()}-(\\d{3,})$`));
|
|
70
|
+
}
|
|
71
|
+
const SUFFIX_STEM_REGEXES = new Map();
|
|
72
|
+
for (const family of SUFFIX_FAMILIES) {
|
|
73
|
+
SUFFIX_STEM_REGEXES.set(family, new RegExp(`^${family.toLowerCase()}-(\\d{3,})(?:-([a-z0-9]+(?:-[a-z0-9]+)*))?$`));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Family -> tree location. Root families (PRD/TAD/BS) live at rcf/ root
|
|
77
|
+
// as single files; child families live one per JSON file under a subdir;
|
|
78
|
+
// AC/TC live inline under their parents and never resolve to a top-level
|
|
79
|
+
// file, so their entry is absent.
|
|
80
|
+
const FAMILY_TO_LOCATION = new Map([
|
|
81
|
+
['REQ', { kind: 'req', subdir: 'requirements', rootFile: null }],
|
|
82
|
+
['US', { kind: 'userStory', subdir: 'user-stories', rootFile: null }],
|
|
83
|
+
['TAC', { kind: 'tac', subdir: 'tacs', rootFile: null }],
|
|
84
|
+
['ADR', { kind: 'adr', subdir: 'adrs', rootFile: null }],
|
|
85
|
+
['FBS', { kind: 'fbs', subdir: 'fbs', rootFile: null }],
|
|
86
|
+
['TS', { kind: 'testSuite', subdir: 'test-suites', rootFile: null }],
|
|
87
|
+
['CN', { kind: 'codeNode', subdir: 'code-nodes', rootFile: null }],
|
|
88
|
+
['PRD', { kind: 'prd', subdir: null, rootFile: 'prd.json' }],
|
|
89
|
+
['TAD', { kind: 'tad', subdir: null, rootFile: 'tad.json' }],
|
|
90
|
+
['BS', { kind: 'buildSequence', subdir: null, rootFile: 'build-sequence.json' }],
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// Family grammar
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Split an id into { family, prefixSlug, digits, suffixSlug }. Returns
|
|
99
|
+
* null when the id matches no known family pattern. The public blueprint
|
|
100
|
+
* surface (`src/blueprint/namespace.js`) re-exports this so callers keep
|
|
101
|
+
* a single import path even though the grammar lives here.
|
|
102
|
+
*
|
|
103
|
+
* @param {unknown} id
|
|
104
|
+
* @returns {{ family: string, prefixSlug: string|null, digits: string, suffixSlug: string|null } | null}
|
|
105
|
+
*/
|
|
106
|
+
export function parseIdParts(id) {
|
|
107
|
+
if (typeof id !== 'string' || id.length === 0) return null;
|
|
108
|
+
for (const [family, regex] of SUFFIX_REGEXES) {
|
|
109
|
+
const match = id.match(regex);
|
|
110
|
+
if (match) return { family, prefixSlug: null, digits: match[1], suffixSlug: match[2] ?? null };
|
|
111
|
+
}
|
|
112
|
+
for (const [family, regex] of PREFIX_REGEXES) {
|
|
113
|
+
const match = id.match(regex);
|
|
114
|
+
if (match) return { family, prefixSlug: match[1] ?? null, digits: match[2], suffixSlug: null };
|
|
115
|
+
}
|
|
116
|
+
for (const [family, regex] of UNNAMESPACED_REGEXES) {
|
|
117
|
+
const match = id.match(regex);
|
|
118
|
+
if (match) return { family, prefixSlug: null, digits: match[1], suffixSlug: null };
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Location of a family in the rcf/ tree, or null when the family is not
|
|
125
|
+
* addressable as a top-level file (AC / TC).
|
|
126
|
+
*
|
|
127
|
+
* @param {string} family
|
|
128
|
+
* @returns {{ kind: string, subdir: string|null, rootFile: string|null } | null}
|
|
129
|
+
*/
|
|
130
|
+
export function familyLocation(family) {
|
|
131
|
+
return FAMILY_TO_LOCATION.get(family) ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Canonicalise a lowercase filename stem to its id form: upper-case ONLY
|
|
136
|
+
* the family segment, leaving any slug prefix / suffix verbatim (they are
|
|
137
|
+
* lower-case kebab by schema construction, matching the on-disk stem).
|
|
138
|
+
*
|
|
139
|
+
* Understands both id families:
|
|
140
|
+
* spa-req-001 -> spa-REQ-001 (prefix family, slug 'spa')
|
|
141
|
+
* req-001 -> REQ-001 (prefix family, no slug)
|
|
142
|
+
* adr-005-spa -> ADR-005-spa (suffix family, slug 'spa')
|
|
143
|
+
* adr-005 -> ADR-005 (suffix family, no slug)
|
|
144
|
+
* fbs-004-user-login -> FBS-004-user-login
|
|
145
|
+
*
|
|
146
|
+
* Returns null when the stem matches no known family pattern; the walker
|
|
147
|
+
* folds defensively in that case so an unrecognised stem surfaces via the
|
|
148
|
+
* downstream load error, not a crash.
|
|
149
|
+
*
|
|
150
|
+
* @param {unknown} stem
|
|
151
|
+
* @returns {string|null}
|
|
152
|
+
*/
|
|
153
|
+
export function canonicaliseStem(stem) {
|
|
154
|
+
if (typeof stem !== 'string' || stem.length === 0) return null;
|
|
155
|
+
// Suffix families first (mirrors parseIdParts: they never carry a
|
|
156
|
+
// leading slug, so their prefix segment is unambiguous).
|
|
157
|
+
for (const [family, regex] of SUFFIX_STEM_REGEXES) {
|
|
158
|
+
const match = stem.match(regex);
|
|
159
|
+
if (match) return match[2] ? `${family}-${match[1]}-${match[2]}` : `${family}-${match[1]}`;
|
|
160
|
+
}
|
|
161
|
+
for (const [family, regex] of PREFIX_STEM_REGEXES) {
|
|
162
|
+
const match = stem.match(regex);
|
|
163
|
+
if (match) return match[1] ? `${match[1]}-${family}-${match[2]}` : `${family}-${match[2]}`;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Numeric identity (leading-zero-tolerant). Independent of the family
|
|
170
|
+
// grammar above -- REQ-001 and REQ-0001 are one id whether or not the
|
|
171
|
+
// blueprint machinery is in play.
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
23
173
|
|
|
24
174
|
/**
|
|
25
175
|
* Strip leading zeros from a run of digits without going through Number
|
package/src/core/store/loader.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
|
|
11
11
|
import { rcfError } from '../errors/index.js';
|
|
12
|
+
import { familyLocation, parseIdParts } from './ids.js';
|
|
12
13
|
import { validateDocument } from './validator.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -41,25 +42,35 @@ const ROOT_FILENAMES = {
|
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
* Resolve an id like "REQ-002"
|
|
45
|
+
* Resolve an id like "REQ-002" (or a blueprint-namespaced id like
|
|
46
|
+
* "spa-REQ-001" / "ADR-005-spa") to a path under rcf/.
|
|
45
47
|
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
+
* The id grammar lives in `./ids.js` (`parseIdParts` / `familyLocation`);
|
|
49
|
+
* this function is the read-side application of it. Prefix-family ids
|
|
50
|
+
* (REQ / US / PRD / BS / TAD / TS) may carry a leading slug namespace;
|
|
51
|
+
* suffix-family ids (ADR / TAC / FBS / CN) may carry a trailing slug
|
|
52
|
+
* namespace. Either way the filename on disk is the lower-cased id
|
|
53
|
+
* (blueprint apply's `destPathFor` writes it that way; the CLI writer
|
|
54
|
+
* matches).
|
|
55
|
+
*
|
|
56
|
+
* Returns null when the id matches no known family pattern OR when the
|
|
57
|
+
* family has no top-level file (AC and TC live inline under their
|
|
58
|
+
* parent US / TS).
|
|
59
|
+
*
|
|
60
|
+
* @param {string} id - canonical id, e.g. "REQ-002", "spa-REQ-001", "ADR-005-spa"
|
|
61
|
+
* @returns {{ kind: string, relPath: string } | null}
|
|
48
62
|
*/
|
|
49
63
|
export function pathForId(id) {
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
//
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
if (id === 'TAD-001' || id.startsWith('TAD-')) return { kind: 'tad', relPath: 'tad.json' };
|
|
61
|
-
if (id === 'BS-001' || id.startsWith('BS-')) return { kind: 'buildSequence', relPath: 'build-sequence.json' };
|
|
62
|
-
return null;
|
|
64
|
+
const parts = parseIdParts(id);
|
|
65
|
+
if (!parts) return null;
|
|
66
|
+
const loc = familyLocation(parts.family);
|
|
67
|
+
if (!loc) return null;
|
|
68
|
+
// Root families (PRD / TAD / BS) resolve to their single root file.
|
|
69
|
+
if (loc.rootFile) return { kind: loc.kind, relPath: loc.rootFile };
|
|
70
|
+
// Child families resolve to <subdir>/<lower(id)>.json. AC / TC have no
|
|
71
|
+
// subdir entry and fall through to null via `!loc` above.
|
|
72
|
+
if (!loc.subdir) return null;
|
|
73
|
+
return { kind: loc.kind, relPath: `${loc.subdir}/${id.toLowerCase()}.json` };
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
/**
|
package/src/core/store/walker.js
CHANGED
|
@@ -16,30 +16,42 @@
|
|
|
16
16
|
// `dependentsByFbsId`, `tsByAcId`, `tcsByAcId`, `usByTacId`.
|
|
17
17
|
|
|
18
18
|
import { rcfError } from '../errors/index.js';
|
|
19
|
-
import { normaliseId } from './ids.js';
|
|
19
|
+
import { canonicaliseStem, normaliseId } from './ids.js';
|
|
20
20
|
import { listSubdirJsonFiles, loadDocument, loadRootDocument, pathForId, subdirFor } from './loader.js';
|
|
21
21
|
import { validateDocument } from './validator.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Derive an id from a filename stem
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `<PREFIX>-<digits>[-<lower-kebab>]`, so the split is always on the
|
|
28
|
-
* FIRST hyphen. Segments after the first hyphen are lower-case by
|
|
29
|
-
* construction (schema pattern `[a-z0-9]+(?:-[a-z0-9]+)*`); we still
|
|
30
|
-
* leave them verbatim rather than round-tripping through
|
|
31
|
-
* .toLowerCase() so a slug that fails the pattern surfaces as-is at the
|
|
32
|
-
* schema-validation step instead of being silently masked here.
|
|
24
|
+
* Derive an id from a filename stem. Delegates to the core grammar in
|
|
25
|
+
* `ids.js` (`canonicaliseStem`), which understands BOTH id families
|
|
26
|
+
* (rcf-schemas 0.4.4):
|
|
33
27
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
28
|
+
* - Prefix families (REQ / US / PRD / BS / TAD / TS) admit an optional
|
|
29
|
+
* kebab-slug PREFIX -- `spa-req-001` -> `spa-REQ-001`.
|
|
30
|
+
* - Suffix families (ADR / TAC / FBS / CN) admit an optional kebab-slug
|
|
31
|
+
* SUFFIX -- `fbs-004-user-login` -> `FBS-004-user-login`,
|
|
32
|
+
* `adr-005-spa` -> `ADR-005-spa`.
|
|
33
|
+
*
|
|
34
|
+
* Before this delegation the walker upper-cased only the FIRST dash
|
|
35
|
+
* segment, which was correct for suffix families but yielded
|
|
36
|
+
* `SPA-req-001` for a prefix-family stem `spa-req-001`. That id then
|
|
37
|
+
* failed `pathForId` at load time and every consuming verb refused with
|
|
38
|
+
* "Unrecognised document id" (w-2026-08-19-003).
|
|
39
|
+
*
|
|
40
|
+
* Defensive fallback: for stems that match no family pattern (a
|
|
41
|
+
* genuinely garbage filename), keep the old first-dash upper-case fold
|
|
42
|
+
* so the walker still records SOMETHING addressable and the subsequent
|
|
43
|
+
* load / validation step surfaces the real error. A crash here would
|
|
44
|
+
* hide the file's true problem.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} stem - filename stem, e.g. `spa-req-001` or `fbs-004-user-login`.
|
|
47
|
+
* @returns {string} canonical id.
|
|
36
48
|
*/
|
|
37
49
|
function idFromFilenameStem(stem) {
|
|
50
|
+
const canonical = canonicaliseStem(stem);
|
|
51
|
+
if (canonical) return canonical;
|
|
38
52
|
const dash = stem.indexOf('-');
|
|
39
53
|
if (dash === -1) return stem.toUpperCase();
|
|
40
|
-
|
|
41
|
-
const tail = stem.slice(dash);
|
|
42
|
-
return `${prefix}${tail}`;
|
|
54
|
+
return `${stem.slice(0, dash).toUpperCase()}${stem.slice(dash)}`;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
57
|
/**
|
package/src/core/store/writer.js
CHANGED
|
@@ -1286,7 +1286,7 @@ export async function deleteDocument({ projectRoot, tree, id, options = {}, walk
|
|
|
1286
1286
|
if (rootKinds.has(kind)) {
|
|
1287
1287
|
return rcfError({
|
|
1288
1288
|
kind: 'usage',
|
|
1289
|
-
message: `delete: root singleton ${id} cannot be deleted via rcf delete`,
|
|
1289
|
+
message: `delete: root singleton ${id} cannot be deleted via rcf define delete`,
|
|
1290
1290
|
documentId: id,
|
|
1291
1291
|
});
|
|
1292
1292
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Deployment-gate barrel. Shared primitives downstream project probes
|
|
2
|
+
// (TAC-210 external-service-dependency provisioning; TAC-211 core-flow
|
|
3
|
+
// end-to-end) import so the placeholder-detection ruleset lives in ONE
|
|
4
|
+
// place and extends via a rcf-lite minor bump, not a per-project fork.
|
|
5
|
+
//
|
|
6
|
+
// Watchpost first-production defect (w-2026-08-24-005, class cure
|
|
7
|
+
// w-2026-08-24-006) is the reason this module exists: the app shipped
|
|
8
|
+
// with RESEND_API_KEY set to a placeholder, the only login path was
|
|
9
|
+
// inert, the gap was filed as a "quirk" note, and sign-off still said
|
|
10
|
+
// DEPLOYED. The SPA blueprint v1.3.0 contributions bind those class
|
|
11
|
+
// rules; this module is the shared enforcement handle.
|
|
12
|
+
|
|
13
|
+
export { detectPlaceholderCredentialShape, PLACEHOLDER_DETECTOR_VERSION } from './placeholder-detector.js';
|