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
package/src/feedback/redact.js
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// secret-shape check the last line of defence, which is what the
|
|
21
21
|
// residual-match refusal in rule 8 leans on.
|
|
22
22
|
|
|
23
|
+
import { randomUUID } from 'node:crypto';
|
|
23
24
|
import { readFileSync } from 'node:fs';
|
|
24
25
|
import { dirname, resolve } from 'node:path';
|
|
25
26
|
import { fileURLToPath } from 'node:url';
|
|
@@ -43,6 +44,14 @@ export const BODY_CAP_BYTES = 8 * 1024;
|
|
|
43
44
|
* conservative: anything a normal profile.md ## Name line might carry
|
|
44
45
|
* as a common English or engineering noun.
|
|
45
46
|
*/
|
|
47
|
+
|
|
48
|
+
// Codex P1-4: per-call collision-safe stash marker suffix. The suffix is
|
|
49
|
+
// 32 hex chars from randomUUID, which is astronomically unlikely to
|
|
50
|
+
// appear as a literal substring in prose the operator would submit.
|
|
51
|
+
function gitStashUuid() {
|
|
52
|
+
return randomUUID().replace(/-/g, '');
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
export const RESERVED_IDENTITY_TOKENS = Object.freeze(new Set([
|
|
47
56
|
'agent', 'agents', 'user', 'users', 'operator', 'operators',
|
|
48
57
|
'admin', 'admins', 'root', 'client', 'clients', 'server', 'servers',
|
|
@@ -435,30 +444,98 @@ export function redact(input, context = {}) {
|
|
|
435
444
|
}
|
|
436
445
|
}
|
|
437
446
|
|
|
447
|
+
// 0.28.3 (Codex P1-1): move the rule-4 allowlist plumbing up so
|
|
448
|
+
// R5f's bare-remote stash can decide whether to preserve or fold the
|
|
449
|
+
// host before it hides the token. Duplicated later in the rule-4
|
|
450
|
+
// block was removed at the same time.
|
|
451
|
+
const allowHosts = new Set([
|
|
452
|
+
...BUNDLED_ALLOWLIST.hosts,
|
|
453
|
+
...(Array.isArray(context.allowHosts) ? context.allowHosts : []),
|
|
454
|
+
].map((h) => h.toLowerCase()));
|
|
455
|
+
const allowExtensions = new Set([
|
|
456
|
+
...(Array.isArray(BUNDLED_ALLOWLIST.extensions) ? BUNDLED_ALLOWLIST.extensions : []),
|
|
457
|
+
...(Array.isArray(context.allowExtensions) ? context.allowExtensions : []),
|
|
458
|
+
].map((e) => String(e).toLowerCase()));
|
|
459
|
+
|
|
460
|
+
// Rule 3-guard (design amendment R5f, issue #243, 0.28.3): protect the
|
|
461
|
+
// git protocol sentinel `git@<host>` before rule 3 runs. In a URL whose
|
|
462
|
+
// scheme is git+ssh/ssh/git, and in the bare git-remote shape
|
|
463
|
+
// `git@<host>:<owner>/<repo>`, `git@<host>` is URI userinfo, not an
|
|
464
|
+
// email. Folding it destroys evidence pointers copied from git remotes
|
|
465
|
+
// and clone URLs. We stash the full matched span before the email regex
|
|
466
|
+
// runs and restore it before rule 4 so the host inside the span is
|
|
467
|
+
// still eligible for the URL / bare-host fold.
|
|
468
|
+
const gitProtoRe = /(?:git\+ssh|ssh|git):\/\/git@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
469
|
+
const gitRemoteBareRe = /(?<![A-Za-z0-9._%+-])git@[A-Za-z0-9.-]+\.[A-Za-z]{2,}(?=:[^\s]+\/)/g;
|
|
470
|
+
const gitStash = [];
|
|
471
|
+
// Use a per-invocation random marker so a literal `GITPROTO0GITPROTO`
|
|
472
|
+
// in the operator's prose cannot be replayed as a restore token
|
|
473
|
+
// (Codex P1-4). The marker is 32 hex chars from randomUUID, wrapped
|
|
474
|
+
// in an unusual sigil (`\u0002...\u0003`) that would not survive
|
|
475
|
+
// the ctrl-char strip pass at rule 8a-late anyway, guaranteeing
|
|
476
|
+
// collision-free restoration for the duration of this call.
|
|
477
|
+
const GIT_STASH_TAG = `GITPROTO_${gitStashUuid()}`;
|
|
478
|
+
// Precompute a bare-remote host allowlist check per candidate:
|
|
479
|
+
// Codex P1-1 called out that a bare `git@<host>:...` remote used
|
|
480
|
+
// to have its host folded via the pre-fix email rule, and the R5f
|
|
481
|
+
// protection accidentally shielded the host too. Restore the host-
|
|
482
|
+
// level allowlist gate here: an allowlisted host stays verbatim,
|
|
483
|
+
// an unallowlisted host has the host portion folded to `<host>`
|
|
484
|
+
// before we stash, so the sentinel-plus-<host> shape survives
|
|
485
|
+
// through the pipeline.
|
|
486
|
+
function stashBareRemote(m) {
|
|
487
|
+
const at = m.indexOf('@');
|
|
488
|
+
const host = m.slice(at + 1);
|
|
489
|
+
if (isHostAllowed(host, allowHosts)) {
|
|
490
|
+
gitStash.push(m);
|
|
491
|
+
} else {
|
|
492
|
+
// Record a hostname ledger row so the operator disclosure names
|
|
493
|
+
// what was folded, matching the shape rule 4 would have used.
|
|
494
|
+
const before = host;
|
|
495
|
+
const after = '<host>';
|
|
496
|
+
// We push a synthetic ledger row for the hostname family (one
|
|
497
|
+
// per bare-remote fold). Deduplicated within the pass.
|
|
498
|
+
ledger.push({ rule: 'hostname', before, after, count: 1 });
|
|
499
|
+
gitStash.push(`git@${after}`);
|
|
500
|
+
}
|
|
501
|
+
return `${GIT_STASH_TAG}${gitStash.length - 1}${GIT_STASH_TAG}`;
|
|
502
|
+
}
|
|
503
|
+
text = text.replace(gitProtoRe, (m) => {
|
|
504
|
+
gitStash.push(m);
|
|
505
|
+
return `${GIT_STASH_TAG}${gitStash.length - 1}${GIT_STASH_TAG}`;
|
|
506
|
+
});
|
|
507
|
+
text = text.replace(gitRemoteBareRe, stashBareRemote);
|
|
508
|
+
|
|
438
509
|
// Rule 3: emails -> <email>. RFC-5322 lite; case-insensitive.
|
|
439
510
|
const emailRe = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
440
511
|
text = replaceRegex(text, emailRe, () => '<email>', 'email', ledger);
|
|
441
512
|
|
|
513
|
+
// R5f: restore git protocol sentinels. The stashed spans now re-enter
|
|
514
|
+
// the pipeline for rule 4 (URL / bare-host). Rule 4's URL regex is
|
|
515
|
+
// extended below to match git+ssh/ssh/git schemes with optional
|
|
516
|
+
// userinfo, so a non-allowlisted host inside a restored clone URL
|
|
517
|
+
// (e.g. `ssh://git@example.internal/owner/repo.git`) folds via that
|
|
518
|
+
// path while the `git@` userinfo is preserved. A bare
|
|
519
|
+
// `git@<host>:<owner>/<repo>` remote is left as-is by rule 4 because
|
|
520
|
+
// it has no scheme prefix and the bareHost lookbehind excludes `@`;
|
|
521
|
+
// the sentinel-protection posture is sufficient for that shape.
|
|
522
|
+
if (gitStash.length > 0) {
|
|
523
|
+
text = text.replace(new RegExp(`${GIT_STASH_TAG}(\\d+)${GIT_STASH_TAG}`, 'g'), (_m, i) => gitStash[Number(i)]);
|
|
524
|
+
}
|
|
525
|
+
|
|
442
526
|
// Rule 4: hostnames and URLs whose registrable domain is not on the
|
|
443
527
|
// allowlist. URLs keep their path suffix; bare hostnames collapse.
|
|
444
528
|
// Loopback (localhost, 127.0.0.1) and everything on the allowlist
|
|
445
529
|
// survive; every other public hostname or URL loses its host.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
// Rule 4 narrowing (0.28.2, issue #233, design amendment R5d): a
|
|
452
|
-
// token whose trailing dot-label is a known file extension is NOT
|
|
453
|
-
// a hostname. This kills the `Backstory-product-brief.md` and
|
|
530
|
+
// allowHosts / allowExtensions declared earlier (Codex P1-1) so the
|
|
531
|
+
// rule-3 bare-remote stash can gate on the host allowlist too. Rule
|
|
532
|
+
// 4 narrowing (0.28.2, issue #233, design amendment R5d): a token
|
|
533
|
+
// whose trailing dot-label is a known file extension is NOT a
|
|
534
|
+
// hostname, killing the `Backstory-product-brief.md` and
|
|
454
535
|
// `intake.json` false positives Barry hit in the WSD round-trip.
|
|
455
536
|
// The bundled shortlist lives in redact-allowlist.json under the
|
|
456
|
-
//
|
|
537
|
+
// `extensions` field; the operator overlay merges via
|
|
457
538
|
// context.allowExtensions.
|
|
458
|
-
const allowExtensions = new Set([
|
|
459
|
-
...(Array.isArray(BUNDLED_ALLOWLIST.extensions) ? BUNDLED_ALLOWLIST.extensions : []),
|
|
460
|
-
...(Array.isArray(context.allowExtensions) ? context.allowExtensions : []),
|
|
461
|
-
].map((e) => String(e).toLowerCase()));
|
|
462
539
|
|
|
463
540
|
// Rule 9: URL-embedded credentials (design amendment R3b, added
|
|
464
541
|
// round 4 for F-P0-B). Runs BEFORE the rule-4 hostname pass so a
|
|
@@ -471,11 +548,17 @@ export function redact(input, context = {}) {
|
|
|
471
548
|
// `url-credential`; false positives are visible in the disclosure.
|
|
472
549
|
text = redactUrlCredentials(text, ledger);
|
|
473
550
|
|
|
474
|
-
// URLs first (http/https), then bare hostnames.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
551
|
+
// URLs first (http/https/git+ssh/ssh/git), then bare hostnames.
|
|
552
|
+
// 0.28.3 (R5f, issue #243): the URL rule accepts git+ssh/ssh/git
|
|
553
|
+
// schemes with an optional userinfo prefix so the host inside a git
|
|
554
|
+
// clone URL can be folded by the same code path that folds an https
|
|
555
|
+
// URL host. The rule-3 guard above has already protected the `git@`
|
|
556
|
+
// sentinel from the email rule; here the sentinel is preserved and
|
|
557
|
+
// only the host portion is folded.
|
|
558
|
+
const urlRe = /(https?|git\+ssh|ssh|git):\/\/((?:[A-Za-z0-9._%+-]+@)?)([A-Za-z0-9.-]+)((?::[0-9]+)?(?:\/[^\s)]*)?)/g;
|
|
559
|
+
text = replaceRegex(text, urlRe, (_m, scheme, userinfo, host, tail) => {
|
|
560
|
+
if (isHostAllowed(host, allowHosts)) return `${scheme}://${userinfo}${host}${tail}`;
|
|
561
|
+
return `${scheme}://${userinfo}<host>${tail}`;
|
|
479
562
|
}, 'hostname', ledger);
|
|
480
563
|
// Bare hostnames: a word that looks like a domain (has a dot,
|
|
481
564
|
// ends with a 2+ letter TLD) and is not on the allowlist. Skip if
|
|
@@ -487,7 +570,10 @@ export function redact(input, context = {}) {
|
|
|
487
570
|
// lookbehind is the 0.28.2 (issue #233) fix that stops
|
|
488
571
|
// `Backstory-product-brief.md` folding on its internal
|
|
489
572
|
// `product-brief.md` slice; the extension-refusal step below is the
|
|
490
|
-
// second layer.
|
|
573
|
+
// second layer. Design amendment R5e (0.28.3, issue #247): the
|
|
574
|
+
// `{2,}` trailing class also functions as the single-letter-TLD
|
|
575
|
+
// exclusion - a token like `a.b.c` has a one-letter tail and is
|
|
576
|
+
// treated as a filename fragment, not a hostname.
|
|
491
577
|
const bareHostRe = /(?<![@/\\\w.\-])(?:[A-Za-z0-9-]+\.)+[A-Za-z]{2,}(?![\w.-])/g;
|
|
492
578
|
text = replaceRegex(text, bareHostRe, (host) => {
|
|
493
579
|
// 0.28.2 (issue #233, design amendment R5d): a candidate whose
|
package/src/intake/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
|
|
3
3
|
export { classifyFidelity, FIDELITY_LEVELS } from './fidelity.js';
|
|
4
4
|
export { scanArtefactForFindings } from './validate.js';
|
|
5
|
-
export { runIntakePhases } from './orchestrator.js';
|
|
5
|
+
export { runIntakePhases, runIntakeScansOnDelta } from './orchestrator.js';
|
|
6
6
|
export { writeIntakeRecord, composeIntakeRecord, nextIntakeId } from './manifest-writer.js';
|
|
@@ -18,6 +18,123 @@ import { classifyFidelity } from './fidelity.js';
|
|
|
18
18
|
import { scanArtefactForFindings } from './validate.js';
|
|
19
19
|
import { composeIntakeRecord } from './manifest-writer.js';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Run the mechanical intake scans over newly minted brief statements
|
|
23
|
+
* against every pre-existing brief statement. The scans operate on
|
|
24
|
+
* the raw text of the statements; a finding is attributed to the
|
|
25
|
+
* ids of every statement whose text the scan matched (an
|
|
26
|
+
* `impliedButNotStated` cue in one new statement with the matching
|
|
27
|
+
* negated-login phrase elsewhere attributes to both). On a frozen
|
|
28
|
+
* tree the caller passes every pre-existing statement as `existing`;
|
|
29
|
+
* the comparison set is `existing` by definition.
|
|
30
|
+
*
|
|
31
|
+
* Each returned finding carries:
|
|
32
|
+
* { kind, detail, scanName, ids: number[] }
|
|
33
|
+
* where `kind` is one of the three mechanical kinds from
|
|
34
|
+
* `scanArtefactForFindings`, `ids` lists the involved statement ids
|
|
35
|
+
* from `newStatements` first and then `existing` (duplicates
|
|
36
|
+
* removed), and `scanName` is `kind` for the DEFINE PR 1 wiring.
|
|
37
|
+
*
|
|
38
|
+
* @param {Array<{ id: number, text?: string }>} newStatements
|
|
39
|
+
* @param {Array<{ id: number, text?: string }>} existing
|
|
40
|
+
* @returns {Array<{ kind: string, detail: string, scanName: string, ids: number[] }>}
|
|
41
|
+
*/
|
|
42
|
+
export function runIntakeScansOnDelta(newStatements, existing) {
|
|
43
|
+
const safeNew = Array.isArray(newStatements) ? newStatements : [];
|
|
44
|
+
const safeExisting = Array.isArray(existing) ? existing : [];
|
|
45
|
+
if (safeNew.length === 0) return [];
|
|
46
|
+
|
|
47
|
+
const combined = [...safeNew, ...safeExisting];
|
|
48
|
+
const combinedText = combined.map((s) => (typeof s?.text === 'string' ? s.text : '')).join('\n');
|
|
49
|
+
const rawFindings = scanArtefactForFindings(combinedText);
|
|
50
|
+
if (rawFindings.length === 0) return [];
|
|
51
|
+
|
|
52
|
+
// A finding only counts when at least one of the newStatements
|
|
53
|
+
// participates. We attribute by re-running the scan over the
|
|
54
|
+
// `newStatements` alone (if that yields the same kind of finding,
|
|
55
|
+
// the new side is sufficient) OR by running the scan over any
|
|
56
|
+
// (new, existing) pair that contains at least one new statement:
|
|
57
|
+
// the second case detects a contradiction between a new and an
|
|
58
|
+
// existing statement.
|
|
59
|
+
/** @type {Array<{ kind: string, detail: string, scanName: string, ids: number[] }>} */
|
|
60
|
+
const out = [];
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
for (const f of rawFindings) {
|
|
63
|
+
const key = `${f.kind}::${f.detail}`;
|
|
64
|
+
if (seen.has(key)) continue;
|
|
65
|
+
seen.add(key);
|
|
66
|
+
const ids = attributeFinding(f, safeNew, safeExisting);
|
|
67
|
+
if (ids.length === 0) continue;
|
|
68
|
+
out.push({ kind: f.kind, detail: f.detail, scanName: f.kind, ids });
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {{ kind: string, detail: string }} finding
|
|
75
|
+
* @param {Array<{ id: number, text?: string }>} newStatements
|
|
76
|
+
* @param {Array<{ id: number, text?: string }>} existing
|
|
77
|
+
* @returns {number[]}
|
|
78
|
+
*/
|
|
79
|
+
function attributeFinding(finding, newStatements, existing) {
|
|
80
|
+
/** @type {number[]} */
|
|
81
|
+
const ids = [];
|
|
82
|
+
for (const s of newStatements) {
|
|
83
|
+
if (statementParticipates(finding, s, [...newStatements, ...existing])) {
|
|
84
|
+
ids.push(Number(s.id));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ids.length === 0) return ids;
|
|
88
|
+
// Also attribute an existing statement that participates on the other side.
|
|
89
|
+
for (const s of existing) {
|
|
90
|
+
if (statementParticipates(finding, s, [...newStatements, ...existing])) {
|
|
91
|
+
if (!ids.includes(Number(s.id))) ids.push(Number(s.id));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return ids;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Decide whether `statement.text` contributes to `finding`. The scan
|
|
99
|
+
* is pattern-based and finds the same finding kind on the text in
|
|
100
|
+
* isolation if the statement itself carries the full pattern; for
|
|
101
|
+
* cross-statement findings (a contradiction between two statements)
|
|
102
|
+
* the statement contributes when it carries at least one half of the
|
|
103
|
+
* pattern and the other half appears somewhere in the combined set.
|
|
104
|
+
*
|
|
105
|
+
* @param {{ kind: string, detail: string }} finding
|
|
106
|
+
* @param {{ id: number, text?: string }} statement
|
|
107
|
+
* @param {Array<{ id: number, text?: string }>} combined
|
|
108
|
+
*/
|
|
109
|
+
function statementParticipates(finding, statement, combined) {
|
|
110
|
+
const text = typeof statement?.text === 'string' ? statement.text : '';
|
|
111
|
+
if (!text) return false;
|
|
112
|
+
const soloFindings = scanArtefactForFindings(text).map((f) => f.kind);
|
|
113
|
+
if (soloFindings.includes(finding.kind)) return true;
|
|
114
|
+
// Cross-statement: pair this statement's text with every other
|
|
115
|
+
// combined text and see whether the pair raises the same finding.
|
|
116
|
+
for (const other of combined) {
|
|
117
|
+
if (other === statement) continue;
|
|
118
|
+
const otherText = typeof other?.text === 'string' ? other.text : '';
|
|
119
|
+
if (!otherText) continue;
|
|
120
|
+
const pairFindings = scanArtefactForFindings(`${text}\n${otherText}`).map((f) => f.kind);
|
|
121
|
+
// Codex review 2026-10-02: a pre-existing finding already
|
|
122
|
+
// raised by `other` alone must not be re-attributed to an
|
|
123
|
+
// unrelated `statement` merely because concatenation leaves
|
|
124
|
+
// the pattern intact. Require the pair to raise the finding
|
|
125
|
+
// that neither side raises alone.
|
|
126
|
+
const otherSolo = scanArtefactForFindings(otherText).map((f) => f.kind);
|
|
127
|
+
if (
|
|
128
|
+
pairFindings.includes(finding.kind)
|
|
129
|
+
&& !soloFindings.includes(finding.kind)
|
|
130
|
+
&& !otherSolo.includes(finding.kind)
|
|
131
|
+
) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
21
138
|
/**
|
|
22
139
|
* @typedef {'napkin'|'productBrief'|'prd'|'tad'|'other'} ArtefactKind
|
|
23
140
|
*/
|