rcf-lite 0.28.2 → 0.29.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 +317 -0
- package/bin/rcf.js +8 -0
- package/fixtures/canary-manifest.json +9 -9
- package/guidance/define-draft-shapes.md +89 -0
- package/guidance/define-intake.md +97 -0
- package/guidance/define-intent.md +107 -0
- package/guidance/discover-prototype.md +67 -0
- package/guidance/discovery-prototypes.md +69 -0
- package/guidance/harness-template.md +34 -0
- package/guidance/managed/agent-instructions-block.hash +1 -1
- package/guidance/managed/agent-instructions-block.md +34 -0
- package/guidance/manifest.json +9 -2
- package/package.json +1 -1
- package/rcf/adrs/adr-4120-canonical-json-hashing.json +30 -0
- package/rcf/adrs/adr-4121-define-sidecars.json +30 -0
- package/rcf/adrs/adr-4122-define-gates-blocking-and-optout.json +30 -0
- package/rcf/adrs/adr-4123-readiness-single-object.json +30 -0
- package/rcf/adrs/adr-4124-define-freeze-verb.json +30 -0
- package/rcf/adrs/adr-4126-persona-tagged-blockers-two-levels.json +40 -0
- package/rcf/adrs/adr-4127-readiness-tab-order-and-register.json +40 -0
- package/rcf/adrs/adr-4129-questions-harness-driven-loop.json +25 -0
- package/rcf/adrs/adr-4130-intake-harness-split-tool-scanned.json +20 -0
- package/rcf/adrs/adr-4132-prototypes-in-discovery-as-evidence.json +20 -0
- package/rcf/adrs/adr-4133-dag-dependency-depth-layout.json +40 -0
- package/rcf/adrs/adr-4134-index-json-route.json +35 -0
- package/rcf/adrs/adr-4135-readiness-po-register-and-adapter.json +35 -0
- package/rcf/adrs/adr-4136-embedding-host-fixture.json +35 -0
- package/rcf/code-nodes/cn-700.json +23 -0
- package/rcf/code-nodes/cn-701.json +14 -0
- package/rcf/code-nodes/cn-702.json +14 -0
- package/rcf/code-nodes/cn-703.json +16 -0
- package/rcf/code-nodes/cn-720.json +15 -0
- package/rcf/code-nodes/cn-721.json +15 -0
- package/rcf/code-nodes/cn-722.json +15 -0
- package/rcf/code-nodes/cn-723.json +18 -0
- package/rcf/code-nodes/cn-724.json +14 -0
- package/rcf/code-nodes/cn-725.json +37 -0
- package/rcf/code-nodes/cn-726.json +25 -0
- package/rcf/code-nodes/cn-727.json +23 -0
- package/rcf/code-nodes/cn-728.json +37 -0
- package/rcf/code-nodes/cn-729.json +26 -0
- package/rcf/code-nodes/cn-730.json +24 -0
- package/rcf/code-nodes/cn-731.json +20 -0
- package/rcf/code-nodes/cn-732.json +14 -0
- package/rcf/code-nodes/cn-733.json +14 -0
- package/rcf/code-nodes/cn-734.json +14 -0
- package/rcf/code-nodes/cn-735.json +20 -0
- package/rcf/code-nodes/cn-736.json +14 -0
- package/rcf/code-nodes/cn-737.json +14 -0
- package/rcf/code-nodes/cn-738.json +14 -0
- package/rcf/code-nodes/cn-739.json +18 -0
- package/rcf/code-nodes/cn-740.json +19 -0
- package/rcf/code-nodes/cn-741.json +16 -0
- package/rcf/code-nodes/cn-742.json +16 -0
- package/rcf/code-nodes/cn-743.json +15 -0
- package/rcf/code-nodes/cn-744.json +22 -0
- package/rcf/code-nodes/cn-745.json +16 -0
- package/rcf/code-nodes/cn-746.json +14 -0
- package/rcf/code-nodes/cn-747.json +18 -0
- package/rcf/code-nodes/cn-748.json +16 -0
- package/rcf/code-nodes/cn-749.json +16 -0
- package/rcf/code-nodes/cn-750.json +15 -0
- package/rcf/code-nodes/cn-751.json +18 -0
- package/rcf/code-nodes/cn-752.json +14 -0
- package/rcf/fbs/fbs-190.json +37 -0
- package/rcf/fbs/fbs-191.json +34 -0
- package/rcf/fbs/fbs-192.json +17 -0
- package/rcf/fbs/fbs-193.json +37 -0
- package/rcf/fbs/fbs-194.json +35 -0
- package/rcf/fbs/fbs-195.json +37 -0
- package/rcf/fbs/fbs-196.json +45 -0
- package/rcf/fbs/fbs-197.json +41 -0
- package/rcf/fbs/fbs-198.json +42 -0
- package/rcf/fbs/fbs-199.json +59 -0
- package/rcf/fbs/fbs-200.json +46 -0
- package/rcf/fbs/fbs-201.json +49 -0
- package/rcf/fbs/fbs-202.json +110 -0
- package/rcf/fbs/fbs-203.json +100 -0
- package/rcf/requirements/req-001.json +2 -1
- package/rcf/requirements/req-002.json +3 -2
- package/rcf/requirements/req-003.json +2 -1
- package/rcf/requirements/req-004.json +2 -1
- package/rcf/requirements/req-005.json +2 -1
- package/rcf/requirements/req-006.json +2 -1
- package/rcf/requirements/req-007.json +2 -1
- package/rcf/requirements/req-008.json +2 -1
- package/rcf/requirements/req-009.json +2 -1
- package/rcf/requirements/req-010.json +5 -2
- package/rcf/requirements/req-011.json +2 -1
- package/rcf/requirements/req-012.json +2 -1
- package/rcf/requirements/req-013.json +2 -1
- package/rcf/requirements/req-014.json +2 -1
- package/rcf/requirements/req-015.json +2 -1
- package/rcf/requirements/req-016.json +5 -2
- package/rcf/requirements/req-017.json +6 -2
- package/rcf/requirements/req-018.json +5 -2
- package/rcf/requirements/req-019.json +5 -2
- package/rcf/requirements/req-020.json +5 -2
- package/rcf/requirements/req-021.json +5 -2
- package/rcf/requirements/req-022.json +5 -2
- package/rcf/requirements/req-023.json +6 -4
- package/rcf/requirements/req-024.json +5 -2
- package/rcf/requirements/req-025.json +5 -2
- package/rcf/requirements/req-026.json +5 -2
- package/rcf/requirements/req-027.json +5 -2
- package/rcf/requirements/req-028.json +5 -2
- package/rcf/requirements/req-029.json +5 -2
- package/rcf/requirements/req-030.json +5 -2
- package/rcf/requirements/req-040.json +5 -2
- package/rcf/requirements/req-041.json +5 -2
- package/rcf/requirements/req-042.json +5 -2
- package/rcf/requirements/req-043.json +5 -2
- package/rcf/requirements/req-050.json +5 -2
- package/rcf/requirements/req-051.json +5 -2
- package/rcf/requirements/req-052.json +5 -2
- package/rcf/requirements/req-053.json +5 -2
- package/rcf/requirements/req-054.json +5 -2
- package/rcf/requirements/req-060.json +5 -2
- package/rcf/requirements/req-061.json +5 -2
- package/rcf/requirements/req-062.json +5 -2
- package/rcf/requirements/req-063.json +5 -2
- package/rcf/requirements/req-070.json +5 -2
- package/rcf/requirements/req-071.json +5 -2
- package/rcf/requirements/req-072.json +5 -2
- package/rcf/requirements/req-073.json +5 -2
- package/rcf/requirements/req-074.json +5 -2
- package/rcf/requirements/req-075.json +5 -2
- package/rcf/requirements/req-076.json +5 -2
- package/rcf/requirements/req-077.json +5 -2
- package/rcf/requirements/req-080.json +5 -2
- package/rcf/requirements/req-081.json +5 -2
- package/rcf/requirements/req-082.json +5 -2
- package/rcf/requirements/req-083.json +5 -2
- package/rcf/requirements/req-084.json +5 -2
- package/rcf/requirements/req-085.json +5 -2
- package/rcf/requirements/req-086.json +5 -2
- package/rcf/requirements/req-090.json +5 -2
- package/rcf/requirements/req-091.json +5 -2
- package/rcf/requirements/req-092.json +5 -2
- package/rcf/requirements/req-093.json +5 -2
- package/rcf/requirements/req-094.json +5 -2
- package/rcf/requirements/req-100.json +5 -2
- package/rcf/requirements/req-101.json +5 -2
- package/rcf/requirements/req-102.json +5 -2
- package/rcf/requirements/req-103.json +5 -2
- package/rcf/requirements/req-104.json +5 -2
- package/rcf/requirements/req-110.json +5 -2
- package/rcf/requirements/req-111.json +5 -2
- package/rcf/requirements/req-112.json +5 -2
- package/rcf/requirements/req-113.json +5 -2
- package/rcf/requirements/req-114.json +5 -2
- package/rcf/requirements/req-115.json +5 -2
- package/rcf/requirements/req-120.json +5 -2
- package/rcf/requirements/req-121.json +5 -2
- package/rcf/requirements/req-122.json +5 -2
- package/rcf/requirements/req-123.json +5 -2
- package/rcf/requirements/req-124.json +5 -2
- package/rcf/requirements/req-125.json +5 -2
- package/rcf/requirements/req-130.json +6 -2
- package/rcf/requirements/req-131.json +6 -2
- package/rcf/requirements/req-132.json +6 -2
- package/rcf/requirements/req-133.json +6 -2
- package/rcf/requirements/req-134.json +6 -2
- package/rcf/requirements/req-140.json +5 -2
- package/rcf/requirements/req-145.json +5 -2
- package/rcf/requirements/req-150.json +6 -2
- package/rcf/requirements/req-151.json +6 -2
- package/rcf/requirements/req-152.json +6 -2
- package/rcf/requirements/req-153.json +6 -2
- package/rcf/requirements/req-154.json +5 -2
- package/rcf/requirements/req-155.json +4 -1
- package/rcf/requirements/req-156.json +4 -1
- package/rcf/requirements/req-157.json +4 -1
- package/rcf/requirements/req-158.json +4 -1
- package/rcf/requirements/req-159.json +4 -1
- package/rcf/requirements/req-160.json +4 -1
- package/rcf/requirements/req-161.json +4 -1
- package/rcf/requirements/req-162.json +4 -1
- package/rcf/requirements/req-163.json +5 -2
- package/rcf/requirements/req-164.json +5 -2
- package/rcf/requirements/req-165.json +5 -2
- package/rcf/requirements/req-170.json +33 -0
- package/rcf/requirements/req-171.json +44 -0
- package/rcf/requirements/req-172.json +25 -0
- package/rcf/requirements/req-173.json +25 -0
- package/rcf/requirements/req-174.json +31 -0
- package/rcf/requirements/req-175.json +31 -0
- package/rcf/requirements/req-176.json +25 -0
- package/rcf/requirements/req-180.json +31 -0
- package/rcf/requirements/req-181.json +31 -0
- package/rcf/requirements/req-182.json +25 -0
- package/rcf/requirements/req-186.json +25 -0
- package/rcf/requirements/req-187.json +31 -0
- package/rcf/requirements/req-188.json +25 -0
- package/rcf/tacs/tac-4120-define-delta-and-freeze.json +21 -0
- package/rcf/tacs/tac-4121-define-ledgers.json +35 -0
- package/rcf/tacs/tac-4122-define-gates.json +61 -0
- package/rcf/tacs/tac-4123-define-readiness.json +61 -0
- package/rcf/tacs/tac-4124-define-freeze.json +28 -0
- package/rcf/tacs/tac-4127-readiness-view.json +49 -0
- package/rcf/tacs/tac-4128-define-guidance.json +53 -0
- package/rcf/tacs/tac-4129-define-questions.json +45 -0
- package/rcf/tacs/tac-4130-define-intake-brief.json +35 -0
- package/rcf/tacs/tac-4131-build-dag-view.json +39 -0
- package/rcf/tacs/tac-4132-id-lookup.json +42 -0
- package/rcf/tacs/tac-4133-readiness-po-layer.json +37 -0
- package/rcf/tacs/tac-4134-wespa-host-fixture.json +54 -0
- package/rcf/test-suites/ts-016.json +3 -3
- package/rcf/test-suites/ts-204.json +30 -0
- package/rcf/test-suites/ts-205.json +22 -0
- package/rcf/test-suites/ts-206.json +22 -0
- package/rcf/test-suites/ts-207.json +22 -0
- package/rcf/test-suites/ts-208.json +22 -0
- package/rcf/test-suites/ts-209.json +22 -0
- package/rcf/test-suites/ts-210.json +22 -0
- package/rcf/test-suites/ts-211.json +22 -0
- package/rcf/test-suites/ts-212.json +22 -0
- package/rcf/test-suites/ts-213.json +50 -0
- package/rcf/test-suites/ts-214.json +74 -0
- package/rcf/test-suites/ts-215.json +211 -0
- package/rcf/test-suites/ts-216.json +94 -0
- package/rcf/test-suites/ts-217.json +62 -0
- package/rcf/test-suites/ts-218.json +78 -0
- package/rcf/test-suites/ts-219.json +70 -0
- package/rcf/test-suites/ts-220.json +78 -0
- package/rcf/test-suites/ts-221.json +78 -0
- package/rcf/test-suites/ts-222.json +100 -0
- package/rcf/test-suites/ts-223.json +94 -0
- package/rcf/test-suites/ts-224.json +70 -0
- package/rcf/test-suites/ts-225.json +78 -0
- package/rcf/test-suites/ts-226.json +158 -0
- package/rcf/test-suites/ts-227.json +38 -0
- package/rcf/test-suites/ts-228.json +86 -0
- package/rcf/test-suites/ts-229.json +83 -0
- package/rcf/user-stories/us-15601.json +4 -4
- package/rcf/user-stories/us-17001.json +27 -0
- package/rcf/user-stories/us-17002.json +22 -0
- package/rcf/user-stories/us-17003.json +22 -0
- package/rcf/user-stories/us-17004.json +22 -0
- package/rcf/user-stories/us-17005.json +22 -0
- package/rcf/user-stories/us-17006.json +21 -0
- package/rcf/user-stories/us-17007.json +21 -0
- package/rcf/user-stories/us-17008.json +22 -0
- package/rcf/user-stories/us-17101.json +22 -0
- package/rcf/user-stories/us-17102.json +22 -0
- package/rcf/user-stories/us-17201.json +38 -0
- package/rcf/user-stories/us-17301.json +38 -0
- package/rcf/user-stories/us-17302.json +50 -0
- package/rcf/user-stories/us-17401.json +70 -0
- package/rcf/user-stories/us-17402.json +55 -0
- package/rcf/user-stories/us-17403.json +60 -0
- package/rcf/user-stories/us-17501.json +48 -0
- package/rcf/user-stories/us-17502.json +60 -0
- package/rcf/user-stories/us-17503.json +65 -0
- package/rcf/user-stories/us-17601.json +58 -0
- package/rcf/user-stories/us-18001.json +55 -0
- package/rcf/user-stories/us-18002.json +35 -0
- package/rcf/user-stories/us-18101.json +35 -0
- package/rcf/user-stories/us-18202.json +40 -0
- package/rcf/user-stories/us-18601.json +55 -0
- package/rcf/user-stories/us-18602.json +50 -0
- package/rcf/user-stories/us-18603.json +45 -0
- package/rcf/user-stories/us-18701.json +50 -0
- package/rcf/user-stories/us-18702.json +50 -0
- package/rcf/user-stories/us-18801.json +45 -0
- package/rcf/user-stories/us-204.json +59 -0
- package/rcf/user-stories/us-205.json +64 -0
- package/rcf/user-stories/us-206.json +54 -0
- package/releases/releases.yaml +31 -1
- package/src/cli/feedback.js +126 -10
- package/src/cli/freeze.js +730 -0
- package/src/cli/help.js +23 -0
- package/src/cli/ledger.js +615 -0
- package/src/cli/questions.js +288 -0
- package/src/cli/readiness.js +498 -0
- package/src/cli/view.js +29 -0
- package/src/core/store/writer.js +86 -6
- package/src/define/freeze-record.js +247 -0
- package/src/define/ledgers.js +712 -0
- package/src/feedback/redact.js +105 -19
- package/src/intake/index.js +1 -1
- package/src/intake/orchestrator.js +117 -0
- package/src/mcp/tools.js +354 -0
- package/src/query/delta.js +282 -0
- package/src/query/gates.js +1107 -0
- package/src/query/index.js +50 -0
- package/src/query/questions.js +438 -0
- package/src/query/readiness.js +574 -0
- package/src/server/index.js +60 -1
- package/src/server/routes.js +133 -4
- package/src/setup/identity-seed.js +17 -0
- package/src/view/build-dag.js +563 -0
- package/src/view/components/badge.js +41 -0
- package/src/view/components/diff.js +72 -0
- package/src/view/components/doc-row.js +62 -0
- package/src/view/components/empty-state.js +35 -0
- package/src/view/components/entity-selector.js +137 -0
- package/src/view/components/filter-bar.js +97 -0
- package/src/view/components/findings-list.js +49 -0
- package/src/view/components/index.js +12 -0
- package/src/view/components/pill.js +46 -0
- package/src/view/components/sub-tab-strip.js +43 -0
- package/src/view/components/toast.js +25 -0
- package/src/view/components-fixture.js +265 -0
- package/src/view/doc-renderers/architecture.js +304 -0
- package/src/view/doc-renderers/build.js +160 -0
- package/src/view/doc-renderers/helpers.js +2 -2
- package/src/view/doc-renderers/index.js +2 -0
- package/src/view/doc-renderers/prd.js +35 -6
- package/src/view/doc-renderers/req.js +10 -2
- package/src/view/doc-renderers/user-story.js +10 -5
- package/src/view/html-page.js +717 -300
- package/src/view/index.js +109 -3
- package/src/view/live-client.js +61 -8
- package/src/view/needs-work.js +79 -0
- package/src/view/page-init.js +2193 -0
- package/src/view/product-map.js +751 -0
- package/src/view/readiness/phrasebook.js +115 -0
- package/src/view/readiness/po-layer.js +288 -0
- package/src/view/readiness/question-adapter.js +329 -0
- package/src/view/readiness/stage-legend.js +71 -0
- package/src/view/readiness.js +505 -0
- package/src/view/style.css +2279 -266
- package/src/view/test-host/embed-client.js +121 -0
- package/src/view/test-host/fixture.js +183 -0
- package/src/view/test-host/lints.js +220 -0
- package/src/view/view-index.js +323 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
// `rcf define questions` subcommand handler (REQ-186; spec 2026-10-01
|
|
2
|
+
// §1.2, §6).
|
|
3
|
+
//
|
|
4
|
+
// Computes and prints the persona question set from readiness and the
|
|
5
|
+
// four ledgers. Pure read: writes NOTHING under rcf/.
|
|
6
|
+
//
|
|
7
|
+
// rcf define questions [--json] [--persona productOwner|engineer]
|
|
8
|
+
// [--stage <D|short>] [--limit <n>]
|
|
9
|
+
//
|
|
10
|
+
// Persona defaults from the profile register (viewer / register
|
|
11
|
+
// marker scan in rcf/.identity/profile.md); when the profile is
|
|
12
|
+
// absent or unstated, the default is productOwner. Unknown persona
|
|
13
|
+
// exits 2 with the usage line naming productOwner and engineer.
|
|
14
|
+
// The verdict exit code lives on `rcf define readiness`; this verb
|
|
15
|
+
// is a diagnostic that always exits 0.
|
|
16
|
+
|
|
17
|
+
import { parseArgs } from 'node:util';
|
|
18
|
+
import { readFile } from 'node:fs/promises';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
|
|
21
|
+
import { resolveTestPointers, walkTree } from '#core/store';
|
|
22
|
+
import { checkCodeNodeResolution } from '#core/store';
|
|
23
|
+
import { findProjectRoot } from '../view/index.js';
|
|
24
|
+
import { loadFreezeRecord } from '../define/freeze-record.js';
|
|
25
|
+
import { loadAllLedgers } from '../define/ledgers.js';
|
|
26
|
+
import { STAGE_ALIASES } from '../query/gates.js';
|
|
27
|
+
import { computeReadiness } from '../query/readiness.js';
|
|
28
|
+
import { computeQuestions, PERSONAS } from '../query/questions.js';
|
|
29
|
+
|
|
30
|
+
const OPTION_SPEC = {
|
|
31
|
+
json: { type: 'boolean' },
|
|
32
|
+
persona: { type: 'string' },
|
|
33
|
+
stage: { type: 'string' },
|
|
34
|
+
limit: { type: 'string' },
|
|
35
|
+
help: { type: 'boolean' },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const HELP = `Usage: rcf define questions [options]
|
|
39
|
+
|
|
40
|
+
Print the persona question set (REQ-186). Each failing product-owner
|
|
41
|
+
check in readiness becomes one plain question per failing item, each
|
|
42
|
+
carrying the exact write-back that answers it.
|
|
43
|
+
|
|
44
|
+
The compute is pure: it never creates or modifies a file under rcf/.
|
|
45
|
+
Answers land through the write-back the question carries; the loop
|
|
46
|
+
ends when 'rcf define readiness --level intent' reports ok.
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--json Emit the full questions object as JSON.
|
|
50
|
+
--persona <who> One of productOwner | engineer. Defaults
|
|
51
|
+
to the register marker in
|
|
52
|
+
rcf/.identity/profile.md, else
|
|
53
|
+
productOwner.
|
|
54
|
+
--stage <stage> Narrow the output to one stage. Accepts
|
|
55
|
+
a stage id (D1..D8) or its short name
|
|
56
|
+
(brief | skeleton | shapes | stories |
|
|
57
|
+
crosscut | consistency | decisions |
|
|
58
|
+
freeze).
|
|
59
|
+
--limit <n> Cap the questions[] array to the first n
|
|
60
|
+
entries. 'remaining' still counts every
|
|
61
|
+
matching item.
|
|
62
|
+
--help Print this help.
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Read `rcf/.identity/profile.md` when it exists; return null on
|
|
67
|
+
* ENOENT or on any read error (the question compute treats absence
|
|
68
|
+
* as "productOwner default" rather than a runtime error).
|
|
69
|
+
*
|
|
70
|
+
* @param {string} projectRoot
|
|
71
|
+
* @returns {Promise<string|null>}
|
|
72
|
+
*/
|
|
73
|
+
async function readProfileText(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
return await readFile(join(projectRoot, 'rcf', '.identity', 'profile.md'), 'utf8');
|
|
76
|
+
} catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Pick the profile register from the text, returning one of
|
|
83
|
+
* `productOwner` | `engineer` | `unstated`.
|
|
84
|
+
*/
|
|
85
|
+
function pickRegister(text) {
|
|
86
|
+
if (typeof text !== 'string' || text.length === 0) return 'unstated';
|
|
87
|
+
// The canonical register template (src/setup/identity-seed.js) names
|
|
88
|
+
// all three markers in its explanation ("- `productOwner`: ..."),
|
|
89
|
+
// then places the chosen value as a bare word on a line of its own
|
|
90
|
+
// ("engineer\n", "unstated\n", etc.). Ad-hoc profiles also use
|
|
91
|
+
// "Register: engineer" on one line. In both shapes the ACTIVE value
|
|
92
|
+
// sits AFTER the explanation, so pick the LAST marker occurrence
|
|
93
|
+
// in the text; a line matching nothing but a marker word takes
|
|
94
|
+
// precedence over an in-prose occurrence at the same position.
|
|
95
|
+
const markers = ['productOwner', 'engineer', 'unstated'];
|
|
96
|
+
// Pass 1: prefer a bare-line marker (canonical template shape).
|
|
97
|
+
const lines = text.split(/\r?\n/);
|
|
98
|
+
let bareLine = null;
|
|
99
|
+
for (const raw of lines) {
|
|
100
|
+
const trimmed = raw.trim();
|
|
101
|
+
if (markers.includes(trimmed)) bareLine = trimmed;
|
|
102
|
+
}
|
|
103
|
+
if (bareLine) return /** @type {any} */ (bareLine);
|
|
104
|
+
// Pass 2: fallback - last occurrence anywhere (handles
|
|
105
|
+
// "Register: engineer" and other operator-prose shapes).
|
|
106
|
+
let bestIdx = -1;
|
|
107
|
+
let best = null;
|
|
108
|
+
for (const m of markers) {
|
|
109
|
+
const idx = text.lastIndexOf(m);
|
|
110
|
+
if (idx > bestIdx) { bestIdx = idx; best = m; }
|
|
111
|
+
}
|
|
112
|
+
return /** @type {any} */ (best) ?? 'unstated';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {string[]} argv
|
|
117
|
+
* @param {object} [deps]
|
|
118
|
+
* @returns {Promise<number>}
|
|
119
|
+
*/
|
|
120
|
+
export async function main(argv, deps = {}) {
|
|
121
|
+
const stdout = deps.stdout ?? process.stdout;
|
|
122
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
123
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
124
|
+
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = parseArgs({ args: argv, options: OPTION_SPEC, allowPositionals: false, strict: true });
|
|
128
|
+
} catch (err) {
|
|
129
|
+
stderr.write(`[error] usage ${err.message}\n`);
|
|
130
|
+
stderr.write(HELP);
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
133
|
+
const flags = parsed.values;
|
|
134
|
+
if (flags.help) {
|
|
135
|
+
stdout.write(HELP);
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Validate --persona before any filesystem work.
|
|
140
|
+
let persona = flags.persona;
|
|
141
|
+
if (persona !== undefined && !PERSONAS.includes(persona)) {
|
|
142
|
+
stderr.write(`[error] usage questions: unknown persona '${persona}' (expected one of ${PERSONAS.join(' | ')})\n`);
|
|
143
|
+
return 2;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Validate --stage against the readiness aliases (same vocabulary).
|
|
147
|
+
let stageFilter = null;
|
|
148
|
+
if (typeof flags.stage === 'string') {
|
|
149
|
+
const canonical = STAGE_ALIASES[flags.stage];
|
|
150
|
+
if (!canonical) {
|
|
151
|
+
stderr.write(`[error] usage questions: unknown --stage '${flags.stage}' (expected D1..D8 or a short name)\n`);
|
|
152
|
+
return 2;
|
|
153
|
+
}
|
|
154
|
+
stageFilter = canonical;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let limit = null;
|
|
158
|
+
if (flags.limit !== undefined) {
|
|
159
|
+
const n = Number(flags.limit);
|
|
160
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
161
|
+
stderr.write(`[error] usage questions: --limit expects a non-negative integer, got '${flags.limit}'\n`);
|
|
162
|
+
return 2;
|
|
163
|
+
}
|
|
164
|
+
limit = n;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
168
|
+
if (!projectRoot) {
|
|
169
|
+
stderr.write('[error] usage no project root found (no rcf/manifest.json in this directory or any ancestor). Run `npx rcf init` to create and wire a project.\n');
|
|
170
|
+
return 2;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const { tree, errors } = await walkTree({ projectRoot });
|
|
174
|
+
const staleErrors = await checkCodeNodeResolution({ projectRoot, tree });
|
|
175
|
+
const validateErrors = [...errors, ...staleErrors];
|
|
176
|
+
|
|
177
|
+
const [freeze, ledgers, testPointers, profileText] = await Promise.all([
|
|
178
|
+
loadFreezeRecord({ projectRoot }),
|
|
179
|
+
loadAllLedgers({ projectRoot }),
|
|
180
|
+
resolveTestPointers({ projectRoot, tree }),
|
|
181
|
+
readProfileText(projectRoot),
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
if (persona === undefined) {
|
|
185
|
+
const register = pickRegister(profileText);
|
|
186
|
+
persona = register === 'engineer' ? 'engineer' : 'productOwner';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const readiness = computeReadiness(tree, {
|
|
190
|
+
freeze, ledgers, profile: undefined, profileText, testPointers, validateErrors,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const result = computeQuestions(readiness, { tree, ledgers, profileText, persona });
|
|
194
|
+
|
|
195
|
+
// Apply --stage and --limit AFTER compute so `remaining` counts
|
|
196
|
+
// every matching item (spec §6).
|
|
197
|
+
let filtered = result.questions;
|
|
198
|
+
if (stageFilter) filtered = filtered.filter((q) => q.stage === stageFilter);
|
|
199
|
+
const remaining = filtered.length;
|
|
200
|
+
if (limit !== null) filtered = filtered.slice(0, limit);
|
|
201
|
+
|
|
202
|
+
// Rebuild groups[] from the filtered list, preserving source-span keys.
|
|
203
|
+
const groupMap = new Map();
|
|
204
|
+
for (const q of filtered) {
|
|
205
|
+
const key = q.context?.statement?.source ?? q.context?.sourceSpan ?? 'tree';
|
|
206
|
+
const existing = groupMap.get(key) ?? { key, label: result.groups.find((g) => g.key === key)?.label ?? key, questionIds: [] };
|
|
207
|
+
existing.questionIds.push(q.id);
|
|
208
|
+
groupMap.set(key, existing);
|
|
209
|
+
}
|
|
210
|
+
const envelope = {
|
|
211
|
+
...result,
|
|
212
|
+
questions: filtered,
|
|
213
|
+
groups: [...groupMap.values()],
|
|
214
|
+
remaining,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (flags.json) {
|
|
218
|
+
stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
renderText(stdout, envelope);
|
|
223
|
+
return 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Print the text summary (spec §1.5): one progress line, then each
|
|
228
|
+
* group with its asks in plain words. Ids, verbs and stage names are
|
|
229
|
+
* not shown unless the owner asks.
|
|
230
|
+
*
|
|
231
|
+
* @param {NodeJS.WritableStream} stdout
|
|
232
|
+
* @param {ReturnType<typeof computeQuestions>} result
|
|
233
|
+
*/
|
|
234
|
+
function renderText(stdout, result) {
|
|
235
|
+
const persona = result.persona;
|
|
236
|
+
// Termination is `result.ok` alone (spec §1.4): --stage filtering
|
|
237
|
+
// can leave remaining=0 for a stage while the whole tree is still
|
|
238
|
+
// failing, which must not print the complete verdict.
|
|
239
|
+
if (result.ok) {
|
|
240
|
+
stdout.write(persona === 'productOwner'
|
|
241
|
+
? 'Intent-complete: yes. The question set is empty.\n'
|
|
242
|
+
: 'Ready-to-build: no engineer blockers in this stage.\n');
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Progress line: "Intent-complete: not yet; N questions left, ..."
|
|
247
|
+
const n = result.remaining;
|
|
248
|
+
const partition = partitionByGroupLabel(result);
|
|
249
|
+
const parts = Object.entries(partition)
|
|
250
|
+
.filter(([, c]) => c > 0)
|
|
251
|
+
.map(([label, c]) => `${c} about ${label}`);
|
|
252
|
+
const progressLine = persona === 'productOwner'
|
|
253
|
+
? `Intent-complete: not yet; ${n} ${n === 1 ? 'question' : 'questions'} left${parts.length ? `, ${parts.join(', ')}` : ''}.`
|
|
254
|
+
: `Ready-to-build: not yet; ${n} ${n === 1 ? 'check' : 'checks'} blocking.`;
|
|
255
|
+
stdout.write(`${progressLine}\n`);
|
|
256
|
+
|
|
257
|
+
for (const group of result.groups) {
|
|
258
|
+
stdout.write(`\n(${group.label})\n`);
|
|
259
|
+
for (const qid of group.questionIds) {
|
|
260
|
+
const q = result.questions.find((x) => x.id === qid);
|
|
261
|
+
if (!q) continue;
|
|
262
|
+
stdout.write(` - ${q.ask}\n`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (persona === 'productOwner' && result.optional?.length) {
|
|
267
|
+
stdout.write('\nOpen decisions (optional, not blocking):\n');
|
|
268
|
+
for (const o of result.optional) {
|
|
269
|
+
stdout.write(` - ${o.ask}\n`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Count questions by group-label bucket for the progress line. For
|
|
276
|
+
* the PO register the two buckets are "your document" (brief:... items)
|
|
277
|
+
* and "requirements" (REQ-... items); anything else goes under "other".
|
|
278
|
+
*/
|
|
279
|
+
function partitionByGroupLabel(result) {
|
|
280
|
+
const buckets = { 'your document': 0, 'requirements': 0, 'other': 0 };
|
|
281
|
+
for (const q of result.questions) {
|
|
282
|
+
const src = q.context?.statement?.source ?? q.context?.sourceSpan ?? 'tree';
|
|
283
|
+
if (typeof src === 'string' && src !== 'tree' && !src.startsWith('scan:')) buckets['your document']++;
|
|
284
|
+
else if (q.context?.reqId) buckets['requirements']++;
|
|
285
|
+
else buckets['other']++;
|
|
286
|
+
}
|
|
287
|
+
return buckets;
|
|
288
|
+
}
|