harness-everything 0.34.0 → 0.36.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.github/workflows/ci.yml +2 -0
- package/AGENTS.md +3 -0
- package/CHANGELOG.md +42 -0
- package/ci/fixtures/doc-obligations/derive/acceptance-only/base/docs/prd/graph.md +22 -0
- package/ci/fixtures/doc-obligations/derive/acceptance-only/candidate/docs/prd/graph.md +22 -0
- package/ci/fixtures/doc-obligations/derive/alias-only/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/alias-only/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/assertion-no-revision-attested/inventory.json +7 -0
- package/ci/fixtures/doc-obligations/derive/attribution-impossible/base/ci/mechanism-open.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/attribution-impossible/candidate/ci/mechanism-open.test.js.fixture +2 -0
- package/ci/fixtures/doc-obligations/derive/clause-context-typo/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/clause-context-typo/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/code-only/base/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/code-only/candidate/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/comment-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/comment-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-assert/base/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-assert/candidate/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-variable/base/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/computed-member-variable/candidate/ci/mechanism-c1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/config.json +11 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/base/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-backtick/candidate/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/base/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/decision-fence-tilde/candidate/docs/adr/GRPH-010.md +20 -0
- package/ci/fixtures/doc-obligations/derive/deep-strict-equal/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deep-strict-equal/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deleted-adr/base/docs/adr/GRPH-008.md +14 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/deleted-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/base/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/base/src/model/a.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/candidate/src/model/b.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-rename/config.json +7 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/docs/model-summary.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/base/src/model.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/index.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/docs/model-summary.md +3 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/candidate/src/model.js +1 -0
- package/ci/fixtures/doc-obligations/derive/derived-two-outputs/config.json +10 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-one/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/base/docs/prd/graph-two/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-one/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/distinct-logical-prds/candidate/docs/prd/graph-two/r001.md +19 -0
- package/ci/fixtures/doc-obligations/derive/docs-only/base/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/docs-only/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-a.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/base/docs/prd/graph-b.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-a.md +20 -0
- package/ci/fixtures/doc-obligations/derive/dup-req-id/candidate/docs/prd/graph-b.md +20 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-literal/base/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-literal/candidate/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/base/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-computed-matcher/candidate/ci/mechanism-e.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/expect-not-to-equal/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/formatting-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/formatting-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/ci/mechanism-helper.test.js +1 -0
- package/ci/fixtures/doc-obligations/derive/helper-delegated-test/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/base/docs/prd/graph/r001.md +25 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r001.md +25 -0
- package/ci/fixtures/doc-obligations/derive/historical-only-req/candidate/docs/prd/graph/r002.md +23 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-current/candidate/docs/prd/graph/r002.md +28 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/ci/mechanism-inplace.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r001.md +22 -0
- package/ci/fixtures/doc-obligations/derive/in-place-historical/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/invalid-clause/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/invalid-clause/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/base/ci/mechanism-legacy.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/legacy-untagged-fallback/candidate/ci/mechanism-legacy.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/literal-whitespace-assert/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/base/ci/mechanism-bad.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/malformed-citation-attribution/candidate/ci/mechanism-bad.test.js.fixture +6 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/malformed-prd/candidate/docs/prd/graph.md +11 -0
- package/ci/fixtures/doc-obligations/derive/missing-approval/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/missing-approval/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/ci/mechanism-mixed.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/ci/mechanism-mixed.test.js.fixture +5 -0
- package/ci/fixtures/doc-obligations/derive/mixed-attribution/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/base/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/mixed-docs-code/candidate/src/lint.js +1 -0
- package/ci/fixtures/doc-obligations/derive/multiline-comment-only/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-comment-only/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-expect/base/ci/mechanism-multi.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/multiline-expect/candidate/ci/mechanism-multi.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-formatting-only/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-numeric-prd/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/multiline-strictequal/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/multiline-strictequal/candidate/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/new-prd/candidate/docs/prd/graph-new.md +20 -0
- package/ci/fixtures/doc-obligations/derive/new-reference/candidate/docs/reference/new-note.md +3 -0
- package/ci/fixtures/doc-obligations/derive/new-req-test/candidate/ci/mechanism-new.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/orphan-req-test/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/base/ci/mechanism-partial.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/base/docs/prd/graph/r001.md +24 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/ci/mechanism-partial.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r001.md +24 -0
- package/ci/fixtures/doc-obligations/derive/partial-revision/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/regex-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/rename/base/docs/reference/old-guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/rename/candidate/docs/reference/guide.md +3 -0
- package/ci/fixtures/doc-obligations/derive/replacement-target/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/replacement-target/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/req-context-typo/base/docs/prd/graph.md +26 -0
- package/ci/fixtures/doc-obligations/derive/req-context-typo/candidate/docs/prd/graph.md +26 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision/candidate/docs/prd/graph/r002.md +28 -0
- package/ci/fixtures/doc-obligations/derive/req-revision-in-place/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/req-revision-in-place/candidate/docs/prd/graph/r001.md +22 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/base/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r001.md +20 -0
- package/ci/fixtures/doc-obligations/derive/retained-revision-chain/candidate/docs/prd/graph/r002.md +26 -0
- package/ci/fixtures/doc-obligations/derive/string-literal-tag/base/ci/mechanism-str.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/string-literal-tag/candidate/ci/mechanism-str.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/base/ci/mechanism-inside.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/tag-inside-test-call/candidate/ci/mechanism-inside.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/tagged-test-prd/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/ci/mechanism-tpl.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/base/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/ci/mechanism-tpl.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-tag/candidate/docs/prd/graph.md +18 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/template-literal-whitespace/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/title-only-rename/base/ci/mechanism-title.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/title-only-rename/candidate/ci/mechanism-title.test.js +3 -0
- package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/base/ci/mechanism-u.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/title-only-unrelated-computed/candidate/ci/mechanism-u.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/base/ci/mechanism-top.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/top-level-no-file-tag/candidate/ci/mechanism-top.test.js +4 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/base/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/trailing-comment-test/candidate/docs/prd/graph.md +20 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/ci/mechanism-two.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/base/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/ci/mechanism-two.test.js +6 -0
- package/ci/fixtures/doc-obligations/derive/two-tests-one-file/candidate/docs/prd/graph.md +24 -0
- package/ci/fixtures/doc-obligations/derive/typo-context/base/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-context/candidate/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-decision/base/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/typo-decision/candidate/docs/adr/GRPH-007.md +14 -0
- package/ci/fixtures/doc-obligations/derive/unbalanced-statement/base/ci/mechanism-multi.test.js +5 -0
- package/ci/fixtures/doc-obligations/derive/unbalanced-statement/candidate/ci/mechanism-multi.test.js.fixture +4 -0
- package/ci/fixtures/doc-obligations/derive/unknown-adr/base/docs/adr/GRPH-009.md +14 -0
- package/ci/fixtures/doc-obligations/derive/unknown-adr/candidate/docs/adr/GRPH-009.md +12 -0
- package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/base/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/unsupported-assertion-syntax/candidate/ci/mechanism-s1.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/whitespace-only-test/base/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/derive/whitespace-only-test/candidate/ci/mechanism-lint.test.js +2 -0
- package/ci/fixtures/doc-obligations/evidence/base/docs/adr/GRPH-007.md +15 -0
- package/ci/fixtures/doc-obligations/evidence/base/docs/prd/PRD-GRPH-001.md +25 -0
- package/ci/fixtures/doc-obligations/evidence/candidate/docs/adr/GRPH-007.md +15 -0
- package/ci/fixtures/doc-obligations/evidence/candidate/docs/prd/PRD-GRPH-001.md +25 -0
- package/ci/mechanism-325-3a-cross.test.js +795 -0
- package/ci/mechanism-325-3a-doc-evidence.test.js +1172 -0
- package/ci/mechanism-325-3a-doc-obligations.test.js +742 -0
- package/ci/mechanism-36-verify-timeout.test.js +534 -0
- package/ci/mechanism-37-portability-lint.test.js +71 -0
- package/ci/mechanism-38-verify-all.test.js +112 -0
- package/ci/mechanism-39-opencode-verify-outcomes.test.js +374 -0
- package/ci/mechanism-pr334-haiku.test.js +298 -0
- package/ci/mechanism-pr334-luna.test.js +192 -0
- package/ci/mechanism-pr334-opencode.test.js +313 -0
- package/ci/mechanism-pr334-r2-boundary.test.js +239 -0
- package/ci/mechanism-pr334-r2-resolution.test.js +243 -0
- package/ci/mechanism-pr334-r2-scope.test.js +127 -0
- package/ci/mechanism-pr334-r3-boundary.test.js +356 -0
- package/ci/mechanism-pr334-r3-citations.test.js +205 -0
- package/ci/mechanism-pr334-r3-headings.test.js +268 -0
- package/ci/portability-lint.js +288 -0
- package/docs/document-lifecycle.md +57 -1
- package/docs/repository-contract.md +1 -1
- package/harness-everything/SKILL.md +1 -1
- package/harness-everything/scripts/doc-evidence.js +471 -0
- package/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/harness-everything/scripts/doc-obligations.js +1571 -0
- package/opencode-plugin/README.md +33 -2
- package/opencode-plugin/hooks/verify.js +87 -11
- package/opencode-plugin/index.mjs +216 -25
- package/opencode-plugin/plugin.json +1 -1
- package/package.json +3 -1
- package/plugins/harness-everything/.codex-plugin/plugin.json +1 -1
- package/plugins/harness-everything/plugin.json +1 -1
- package/plugins/harness-everything/skills/harness-everything/SKILL.md +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-evidence.js +471 -0
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-lifecycle.js +1 -1
- package/plugins/harness-everything/skills/harness-everything/scripts/doc-obligations.js +1571 -0
- package/sbom.cdx.json +7 -7
- package/scripts/verify-all.js +314 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// PR #334 round 3 — cross-module boundary regression.
|
|
4
|
+
//
|
|
5
|
+
// Finding 4307119827 (requirement identity): a REQ retarget REQ-A -> REQ-B with an
|
|
6
|
+
// identical test body was silently paired by body equality and reported as
|
|
7
|
+
// requirement-sync N/A. Body sameness is not proof of a stable REQ-to-test
|
|
8
|
+
// association. A same-REQ display-title rename must still stay N/A, but any changed
|
|
9
|
+
// REQ set (retarget, add, remove, multi) must retain the new/changed REQ, be
|
|
10
|
+
// applicable or conservatively unknown, and never degrade to N/A. Unknown
|
|
11
|
+
// applicability can never pass or fail even with bound passing/failing records.
|
|
12
|
+
//
|
|
13
|
+
// Finding 4307119837 (ADR Decision heading): extractSection recognized only
|
|
14
|
+
// column-zero ATX headings, so a 1-3-space-indented `## Decision` was missed and its
|
|
15
|
+
// normative content change produced no quality-review/conflict-check MUST. Per
|
|
16
|
+
// CommonMark 0.31.2 (https://spec.commonmark.org/0.31.2/#atx-headings) an ATX
|
|
17
|
+
// heading may be indented 0-3 spaces; 4 spaces or a tab is an indented code block and
|
|
18
|
+
// a fenced `#` is not a heading. An indented next section still terminates Decision.
|
|
19
|
+
//
|
|
20
|
+
// Every case drives the full deriveDocFacts -> deriveDocObligations ->
|
|
21
|
+
// checkDocObligations pipeline using the existing valid REQ fixture and the existing
|
|
22
|
+
// typo-decision / decision-fence fixtures, so it catches regressions that only appear
|
|
23
|
+
// across the module boundary.
|
|
24
|
+
|
|
25
|
+
const assert = require('assert');
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
const crypto = require('crypto');
|
|
29
|
+
const helper = require('./test-helper');
|
|
30
|
+
|
|
31
|
+
const obligationsPath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-obligations.js');
|
|
32
|
+
const evidencePath = path.join(__dirname, '..', 'harness-everything', 'scripts', 'doc-evidence.js');
|
|
33
|
+
const fixtureRoot = path.join(__dirname, 'fixtures', 'doc-obligations', 'derive');
|
|
34
|
+
const CONFIG = JSON.parse(fs.readFileSync(path.join(fixtureRoot, 'config.json'), 'utf8'));
|
|
35
|
+
|
|
36
|
+
let deriveDocFacts, deriveDocObligations, checkDocObligations;
|
|
37
|
+
try {
|
|
38
|
+
({ deriveDocFacts, deriveDocObligations } = require(obligationsPath));
|
|
39
|
+
({ checkDocObligations } = require(evidencePath));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
helper.check('pr334-r3-boundary. derive/evidence modules are available', false, error.message);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log('\n[pr334-r3-boundary] REQ identity + ATX heading cross-module matrix...');
|
|
45
|
+
|
|
46
|
+
const BASELINE = 'sha256:pr334-r3-boundary-baseline';
|
|
47
|
+
const RETARGET_TEST = 'ci/pr334-r3-retarget.test.js';
|
|
48
|
+
const REQ_FILE = 'docs/prd/graph/r001.md';
|
|
49
|
+
const ADR_PATH = 'docs/adr/GRPH-007.md';
|
|
50
|
+
|
|
51
|
+
function fixtureFile(name, side, rel) {
|
|
52
|
+
return fs.readFileSync(path.join(fixtureRoot, name, side, rel), 'utf8');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Valid, effective PRD declaring REQ-GRPH-010 and REQ-GRPH-011 (both retained).
|
|
56
|
+
const VALID_PRD = fixtureFile('req-revision', 'base', REQ_FILE);
|
|
57
|
+
const TYPO_BASE = fixtureFile('typo-decision', 'base', ADR_PATH);
|
|
58
|
+
|
|
59
|
+
function checkCase(name, body) {
|
|
60
|
+
try {
|
|
61
|
+
body();
|
|
62
|
+
helper.check(name, true);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
helper.check(name, false, error && error.message ? error.message : String(error));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Requirement-sync: derive -> obligations -> evidence
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
function derivePipeline(baseFiles, candidateFiles) {
|
|
73
|
+
const { facts } = deriveDocFacts({
|
|
74
|
+
base: { files: baseFiles },
|
|
75
|
+
candidate: { files: candidateFiles },
|
|
76
|
+
config: CONFIG,
|
|
77
|
+
inventory: { complete: true }
|
|
78
|
+
});
|
|
79
|
+
const requirementSync = deriveDocObligations(facts).find(item => item.capability === 'requirement-sync');
|
|
80
|
+
assert.ok(requirementSync, 'requirement-sync is in the derived obligation catalog');
|
|
81
|
+
return { facts, obligation: requirementSync };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function reqScenario(baseTest, candidateTest) {
|
|
85
|
+
const baseFiles = { [REQ_FILE]: VALID_PRD, [RETARGET_TEST]: baseTest };
|
|
86
|
+
const candidateFiles = { [REQ_FILE]: VALID_PRD, [RETARGET_TEST]: candidateTest };
|
|
87
|
+
const { facts, obligation } = derivePipeline(baseFiles, candidateFiles);
|
|
88
|
+
return { facts, obligation, candidateFiles };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sha256(text) {
|
|
92
|
+
return `sha256:${crypto.createHash('sha256').update(text, 'utf8').digest('hex')}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Binds a bound, complete execution record to every scope path of the obligation.
|
|
96
|
+
function checkWithEvidence(obligation, candidateFiles, exitCode) {
|
|
97
|
+
const artifacts = {};
|
|
98
|
+
const executionRecords = [];
|
|
99
|
+
for (const targetPath of obligation.scope.paths) {
|
|
100
|
+
const text = candidateFiles[targetPath];
|
|
101
|
+
if (typeof text !== 'string') continue;
|
|
102
|
+
const digest = sha256(text);
|
|
103
|
+
artifacts[targetPath] = { digest };
|
|
104
|
+
executionRecords.push({
|
|
105
|
+
id: `exec-${executionRecords.length}`,
|
|
106
|
+
obligationId: obligation.id,
|
|
107
|
+
command: `npm run verify -- ${targetPath}`,
|
|
108
|
+
provenance: `bound execution record for ${targetPath}`,
|
|
109
|
+
exitCode,
|
|
110
|
+
baseDigest: BASELINE,
|
|
111
|
+
artifactDigests: { [targetPath]: digest },
|
|
112
|
+
paths: [targetPath]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return checkDocObligations({
|
|
116
|
+
obligations: [obligation],
|
|
117
|
+
artifacts,
|
|
118
|
+
baseline: { digest: BASELINE },
|
|
119
|
+
executionRecords,
|
|
120
|
+
reviewRecords: []
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resultFor(scenario, exitCode) {
|
|
125
|
+
return checkWithEvidence(scenario.obligation, scenario.candidateFiles, exitCode).results[0];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isUnknownScope(obligation) {
|
|
129
|
+
return obligation.scope && obligation.scope.requirementIds === 'unknown';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// A changed REQ set is never N/A; its bound evidence never passes and, when the
|
|
133
|
+
// applicability is conservatively unknown, it never passes OR fails.
|
|
134
|
+
function assertChangedSetNotNA(label, scenario) {
|
|
135
|
+
const { facts, obligation } = scenario;
|
|
136
|
+
assert.ok(['applicable', 'unknown'].includes(obligation.applicability),
|
|
137
|
+
`${label}: a changed REQ set is applicable or unknown, never N/A (got ${JSON.stringify(obligation.applicability)})`);
|
|
138
|
+
assert.notStrictEqual(facts.requirementSyncTrigger === null && !facts.requirementScopeUnknown, true,
|
|
139
|
+
`${label}: the change is not silently trivial`);
|
|
140
|
+
|
|
141
|
+
const passing = resultFor(scenario, 0);
|
|
142
|
+
const failing = resultFor(scenario, 1);
|
|
143
|
+
assert.notStrictEqual(passing.outcome, 'pass', `${label}: a bare REQ-set change with no revision evidence never passes`);
|
|
144
|
+
assert.notStrictEqual(failing.outcome, 'pass', `${label}: a bare REQ-set change with no revision evidence never passes`);
|
|
145
|
+
if (obligation.applicability === 'unknown') {
|
|
146
|
+
assert.strictEqual(passing.outcome, 'none', `${label}: unknown applicability with bound passing records is never pass`);
|
|
147
|
+
assert.strictEqual(failing.outcome, 'none', `${label}: unknown applicability with bound failing records is never fail`);
|
|
148
|
+
assert.strictEqual(failing.observation, 'not-observed', `${label}: unknown applicability fails closed`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function scopedTest(scenario) {
|
|
153
|
+
return scenario.facts.reqCitingTests.includes(RETARGET_TEST)
|
|
154
|
+
|| scenario.obligation.scope.paths.includes(RETARGET_TEST);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
checkCase('pr334-r3-boundary. same-REQ display-title rename stays requirement-sync N/A (control)', () => {
|
|
158
|
+
const scenario = reqScenario(
|
|
159
|
+
"test('REQ-GRPH-011 old title', () => { verify(); });\n",
|
|
160
|
+
"test('REQ-GRPH-011 renamed title', () => { verify(); });\n"
|
|
161
|
+
);
|
|
162
|
+
assert.strictEqual(scenario.facts.requirementSyncTrigger, null, 'a display-title rename is not a new test or assertion edit');
|
|
163
|
+
assert.strictEqual(scenario.facts.requirementScopeUnknown, false, 'the unchanged body and REQ identity are stable');
|
|
164
|
+
assert.deepStrictEqual(scenario.facts.citedReqs, [], 'a display-title rename contributes no changed citation');
|
|
165
|
+
assert.strictEqual(scenario.obligation.applicability, 'N/A', 'a same-REQ display-title rename is not a sync candidate');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
checkCase('pr334-r3-boundary. REQ retarget REQ-GRPH-011 -> REQ-GRPH-010 with identical body is never N/A and retains the new REQ', () => {
|
|
169
|
+
const scenario = reqScenario(
|
|
170
|
+
"test('REQ-GRPH-011 stable', () => { verify(); });\n",
|
|
171
|
+
"test('REQ-GRPH-010 stable', () => { verify(); });\n"
|
|
172
|
+
);
|
|
173
|
+
assertChangedSetNotNA('retarget 011->010 identical body', scenario);
|
|
174
|
+
|
|
175
|
+
const unknown = isUnknownScope(scenario.obligation);
|
|
176
|
+
const retained = scenario.facts.citedReqs.some(item => item.id === 'REQ-GRPH-010');
|
|
177
|
+
assert.ok(retained || unknown,
|
|
178
|
+
`the retargeted new REQ-GRPH-010 is retained or the scope is explicitly unknown: ${JSON.stringify(scenario.facts.citedReqs)}`);
|
|
179
|
+
assert.ok(scopedTest(scenario) || unknown,
|
|
180
|
+
`the changed test stays in the requirement-sync scope: ${JSON.stringify(scenario.obligation.scope.paths)}`);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
checkCase('pr334-r3-boundary. adding a REQ id to an existing test title is never N/A and retains the added REQ', () => {
|
|
184
|
+
const scenario = reqScenario(
|
|
185
|
+
"test('stable', () => { verify(); });\n",
|
|
186
|
+
"test('REQ-GRPH-011 stable', () => { verify(); });\n"
|
|
187
|
+
);
|
|
188
|
+
assertChangedSetNotNA('add id', scenario);
|
|
189
|
+
|
|
190
|
+
const unknown = isUnknownScope(scenario.obligation);
|
|
191
|
+
const retained = scenario.facts.citedReqs.some(item => item.id === 'REQ-GRPH-011');
|
|
192
|
+
assert.ok(retained || unknown,
|
|
193
|
+
`the added REQ-GRPH-011 is retained or the scope is explicitly unknown: ${JSON.stringify(scenario.facts.citedReqs)}`);
|
|
194
|
+
assert.ok(scopedTest(scenario) || unknown,
|
|
195
|
+
`the changed test stays in the requirement-sync scope: ${JSON.stringify(scenario.obligation.scope.paths)}`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
checkCase('pr334-r3-boundary. removing a REQ id from an existing test title is never N/A and keeps the changed test in scope', () => {
|
|
199
|
+
const scenario = reqScenario(
|
|
200
|
+
"test('REQ-GRPH-011 stable', () => { verify(); });\n",
|
|
201
|
+
"test('stable', () => { verify(); });\n"
|
|
202
|
+
);
|
|
203
|
+
assertChangedSetNotNA('remove id', scenario);
|
|
204
|
+
assert.ok(scopedTest(scenario) || isUnknownScope(scenario.obligation),
|
|
205
|
+
`a changed REQ set keeps the changed test in scope: ${JSON.stringify(scenario.obligation.scope.paths)}`);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
checkCase('pr334-r3-boundary. multi-REQ set change is never N/A and keeps the changed test in scope', () => {
|
|
209
|
+
const scenario = reqScenario(
|
|
210
|
+
"test('REQ-GRPH-010 REQ-GRPH-011 multi', () => { verify(); });\n",
|
|
211
|
+
"test('REQ-GRPH-011 multi', () => { verify(); });\n"
|
|
212
|
+
);
|
|
213
|
+
assertChangedSetNotNA('multi set change', scenario);
|
|
214
|
+
const unknown = isUnknownScope(scenario.obligation);
|
|
215
|
+
assert.ok(scenario.facts.citedReqs.some(item => item.id === 'REQ-GRPH-011') || unknown,
|
|
216
|
+
`the surviving REQ-GRPH-011 is retained or the scope is explicitly unknown: ${JSON.stringify(scenario.facts.citedReqs)}`);
|
|
217
|
+
assert.ok(scopedTest(scenario) || unknown,
|
|
218
|
+
`the changed test stays in the requirement-sync scope: ${JSON.stringify(scenario.obligation.scope.paths)}`);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
checkCase('pr334-r3-boundary. reordering identical same-REQ calls stays N/A (control)', () => {
|
|
222
|
+
const scenario = reqScenario(
|
|
223
|
+
[
|
|
224
|
+
"test('REQ-GRPH-011 first', () => { verifyFirst(); });",
|
|
225
|
+
"test('REQ-GRPH-011 second', () => { verifySecond(); });"
|
|
226
|
+
].join('\n') + '\n',
|
|
227
|
+
[
|
|
228
|
+
"test('REQ-GRPH-011 second', () => { verifySecond(); });",
|
|
229
|
+
"test('REQ-GRPH-011 first', () => { verifyFirst(); });"
|
|
230
|
+
].join('\n') + '\n'
|
|
231
|
+
);
|
|
232
|
+
assert.notStrictEqual(scenario.facts.requirementSyncTrigger, 'new-test', 'the same REQ call inventory is unchanged');
|
|
233
|
+
assert.strictEqual(scenario.facts.requirementScopeUnknown, false, 'both existing calls retain stable identities');
|
|
234
|
+
assert.strictEqual(scenario.obligation.applicability, 'N/A', 'a pure reorder is not a sync candidate');
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
checkCase('pr334-r3-boundary. genuine added REQ-citing test stays applicable and keeps the observed-fail lane (control)', () => {
|
|
238
|
+
const scenario = reqScenario(
|
|
239
|
+
"test('REQ-GRPH-011 existing', () => { verifyA(); });\n",
|
|
240
|
+
[
|
|
241
|
+
"test('REQ-GRPH-011 existing', () => { verifyA(); });",
|
|
242
|
+
"test('REQ-GRPH-011 added', verifyB);"
|
|
243
|
+
].join('\n') + '\n'
|
|
244
|
+
);
|
|
245
|
+
assert.strictEqual(scenario.facts.requirementSyncTrigger, 'new-test', 'the added REQ-citing test is a new-test signal');
|
|
246
|
+
assert.strictEqual(scenario.obligation.applicability, 'applicable', 'a genuine added test is a sync candidate');
|
|
247
|
+
assert.ok(scenario.facts.citedReqs.some(item => item.id === 'REQ-GRPH-011' && item.resolution === 'resolved'),
|
|
248
|
+
`the added test cites a resolved REQ: ${JSON.stringify(scenario.facts.citedReqs)}`);
|
|
249
|
+
const failing = resultFor(scenario, 1);
|
|
250
|
+
assert.strictEqual(failing.observation, 'observed', 'a resolved known identity keeps the observed lane');
|
|
251
|
+
assert.strictEqual(failing.outcome, 'fail', 'a resolved cited REQ with bound failing evidence is observed fail');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
checkCase('pr334-r3-boundary. existing REQ assertion change stays applicable and keeps the observed-fail lane (control)', () => {
|
|
255
|
+
const scenario = reqScenario(
|
|
256
|
+
"// REQ-GRPH-011\nassert.equal(run(), 1);\n",
|
|
257
|
+
"// REQ-GRPH-011\nassert.equal(run(), 2);\n"
|
|
258
|
+
);
|
|
259
|
+
assert.strictEqual(scenario.facts.requirementSyncTrigger, 'assertion-change', 'the changed assertion triggers requirement-sync');
|
|
260
|
+
assert.strictEqual(scenario.obligation.applicability, 'applicable', 'a genuine assertion change is a sync candidate');
|
|
261
|
+
assert.ok(scenario.facts.citedReqs.some(item => item.id === 'REQ-GRPH-011' && item.resolution === 'resolved'),
|
|
262
|
+
`the changed assertion cites a resolved REQ: ${JSON.stringify(scenario.facts.citedReqs)}`);
|
|
263
|
+
assert.strictEqual(resultFor(scenario, 1).outcome, 'fail', 'a resolved cited REQ with bound failing evidence is observed fail');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
checkCase('pr334-r3-boundary. unknown requirement identity can never pass or fail even with bound records (control)', () => {
|
|
267
|
+
const scenario = reqScenario(
|
|
268
|
+
"test('REQ-GRPH-011 old check', () => { verifyOldBehavior(); });\n",
|
|
269
|
+
"test('REQ-GRPH-011 replacement check', () => { verifyNewBehavior(); });\n"
|
|
270
|
+
);
|
|
271
|
+
assert.strictEqual(scenario.facts.requirementScopeUnknown, true, 'a same-REQ wholesale body replacement leaves identity ambiguous');
|
|
272
|
+
assert.strictEqual(scenario.obligation.applicability, 'unknown', 'ambiguous identity is unknown, never downgraded to N/A');
|
|
273
|
+
assert.strictEqual(isUnknownScope(scenario.obligation), true, 'the obligation carries an explicitly unknown requirement scope');
|
|
274
|
+
|
|
275
|
+
const passing = resultFor(scenario, 0);
|
|
276
|
+
const failing = resultFor(scenario, 1);
|
|
277
|
+
assert.strictEqual(passing.outcome, 'none', 'unknown applicability with bound passing evidence is never pass');
|
|
278
|
+
assert.strictEqual(failing.outcome, 'none', 'unknown applicability with bound failing evidence is never fail');
|
|
279
|
+
assert.strictEqual(failing.observation, 'not-observed', 'unknown applicability fails closed');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ---------------------------------------------------------------------------
|
|
283
|
+
// ADR Decision heading: derive -> obligations (CommonMark ATX indentation)
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
function deriveAdr(baseText, candidateText) {
|
|
287
|
+
const { facts } = deriveDocFacts({
|
|
288
|
+
base: { files: { [ADR_PATH]: baseText } },
|
|
289
|
+
candidate: { files: { [ADR_PATH]: candidateText } },
|
|
290
|
+
config: CONFIG
|
|
291
|
+
});
|
|
292
|
+
const byCapability = new Map(deriveDocObligations(facts).map(item => [item.capability, item]));
|
|
293
|
+
return { facts, byCapability };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function assertDecisionMUSTs(label, baseText, candidateText) {
|
|
297
|
+
const { facts, byCapability } = deriveAdr(baseText, candidateText);
|
|
298
|
+
assert.strictEqual(facts.normativeChanged, true, `${label}: the Decision content change is normative`);
|
|
299
|
+
for (const capability of ['quality-review', 'conflict-check']) {
|
|
300
|
+
assert.strictEqual(byCapability.get(capability).applicability, 'applicable',
|
|
301
|
+
`${label}: ${capability} is a MUST for a Decision change`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function assertNonNormative(label, baseText, candidateText) {
|
|
306
|
+
const { byCapability } = deriveAdr(baseText, candidateText);
|
|
307
|
+
for (const capability of ['quality-review', 'conflict-check']) {
|
|
308
|
+
assert.strictEqual(byCapability.get(capability).applicability, 'N/A',
|
|
309
|
+
`${label}: ${capability} must not fire for a non-Decision change`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Reuses the typo-decision fixture, differing only in "Run lint" -> "Run lnt".
|
|
314
|
+
function decisionWithPad(pad) {
|
|
315
|
+
const base = TYPO_BASE;
|
|
316
|
+
const candidate = fixtureFile('typo-decision', 'candidate', ADR_PATH);
|
|
317
|
+
const rewrite = text => text.replace('\n## Decision\n', `\n${pad}## Decision\n`);
|
|
318
|
+
return [rewrite(base), rewrite(candidate)];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (const [label, pad] of [['0-space', ''], ['1-space', ' '], ['2-space', ' '], ['3-space', ' ']]) {
|
|
322
|
+
checkCase(`pr334-r3-boundary. a ${label}-indented Decision heading is a real ATX heading and fires the normative MUSTs`, () => {
|
|
323
|
+
const [base, candidate] = decisionWithPad(pad);
|
|
324
|
+
assertDecisionMUSTs(label, base, candidate);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
for (const [label, pad] of [['4-space', ' '], ['tab', '\t']]) {
|
|
329
|
+
checkCase(`pr334-r3-boundary. a ${label}-indented "## Decision" is an indented code block, not a heading (negative)`, () => {
|
|
330
|
+
const [base, candidate] = decisionWithPad(pad);
|
|
331
|
+
assertNonNormative(label, base, candidate);
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (const name of ['decision-fence-backtick', 'decision-fence-tilde']) {
|
|
336
|
+
checkCase(`pr334-r3-boundary. a fenced fake heading does not terminate Decision (${name}, negative)`, () => {
|
|
337
|
+
const base = fixtureFile(name, 'base', 'docs/adr/GRPH-010.md');
|
|
338
|
+
const candidate = fixtureFile(name, 'candidate', 'docs/adr/GRPH-010.md');
|
|
339
|
+
assertDecisionMUSTs(name, base, candidate);
|
|
340
|
+
assert.strictEqual(base.includes('```') || base.includes('~~~'), true, `${name}: fixture carries a fence`);
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Reuses the typo-decision fixture (Decision unchanged) and appends a next section
|
|
345
|
+
// whose heading indentation varies; only the next-section text changes.
|
|
346
|
+
function sectioned(pad, tail) {
|
|
347
|
+
return `${TYPO_BASE}\n${pad}## Consequences\n\n${tail}\n`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (const [label, pad] of [['0-space', ''], ['1-space', ' '], ['2-space', ' ']]) {
|
|
351
|
+
checkCase(`pr334-r3-boundary. a ${label}-indented next section terminates Decision (change outside is non-normative)`, () => {
|
|
352
|
+
assertNonNormative(label, sectioned(pad, 'Consequence A.'), sectioned(pad, 'Consequence B.'));
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
helper.finish();
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const assert = require('assert');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const helper = require('./test-helper');
|
|
6
|
+
|
|
7
|
+
const { deriveDocFacts, deriveDocObligations } = require(path.join(
|
|
8
|
+
__dirname,
|
|
9
|
+
'..',
|
|
10
|
+
'harness-everything',
|
|
11
|
+
'scripts',
|
|
12
|
+
'doc-obligations.js'
|
|
13
|
+
));
|
|
14
|
+
|
|
15
|
+
const TEST_PATH = 'ci/pr334-citations.test.js';
|
|
16
|
+
const A = 'REQ-GRPH-001';
|
|
17
|
+
const B = 'REQ-GRPH-002';
|
|
18
|
+
const C = 'REQ-GRPH-003';
|
|
19
|
+
|
|
20
|
+
function derive(beforeText, afterText) {
|
|
21
|
+
const result = deriveDocFacts({
|
|
22
|
+
base: { files: { [TEST_PATH]: beforeText } },
|
|
23
|
+
candidate: { files: { [TEST_PATH]: afterText } },
|
|
24
|
+
config: {}
|
|
25
|
+
});
|
|
26
|
+
const requirementSync = deriveDocObligations(result.facts)
|
|
27
|
+
.find(item => item.capability === 'requirement-sync');
|
|
28
|
+
return { facts: result.facts, requirementSync };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function checkCase(name, run) {
|
|
32
|
+
try {
|
|
33
|
+
run();
|
|
34
|
+
helper.check(name, true);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
helper.check(name, false, error && error.message ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertUnknownCitation({ facts, requirementSync }, expectedIds, reason) {
|
|
41
|
+
assert.strictEqual(facts.requirementScopeUnknown, true, reason);
|
|
42
|
+
assert.strictEqual(requirementSync.applicability, 'unknown', reason);
|
|
43
|
+
assert.strictEqual(requirementSync.scope.requirementIds, 'unknown', reason);
|
|
44
|
+
assert.deepStrictEqual(facts.citedReqs.map(item => item.id), expectedIds, 'candidate REQ identities remain visible');
|
|
45
|
+
assert.ok(facts.reqCitingTests.includes(TEST_PATH), 'the candidate test path remains visible');
|
|
46
|
+
assert.ok(requirementSync.scope.paths.includes(TEST_PATH), 'unknown candidate path remains in obligation scope');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
checkCase('R3 retargeting test citation A to B with the same body is unknown and retains B', () => {
|
|
50
|
+
const result = derive(
|
|
51
|
+
`test('${A} stable title', () => { verifyShared(); });\n`,
|
|
52
|
+
`test('${B} stable title', () => { verifyShared(); });\n`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'a retarget is not misclassified as a newly added test');
|
|
56
|
+
assertUnknownCitation(result, [B], 'body equality cannot make a changed REQ association known');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
checkCase('R3 adding citation A to an existing test with the same body is unknown and retains A', () => {
|
|
60
|
+
const result = derive(
|
|
61
|
+
"test('unchanged title', () => { verifyShared(); });\n",
|
|
62
|
+
`test('${A} unchanged title', () => { verifyShared(); });\n`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'a citation change is not classified as a new test');
|
|
66
|
+
assertUnknownCitation(result, [A], 'the added candidate citation is not silently dropped');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
checkCase('R3 removing citation A from an existing test with the same body stays unknown, never N/A', () => {
|
|
70
|
+
const result = derive(
|
|
71
|
+
`test('${A} unchanged title', () => { verifyShared(); });\n`,
|
|
72
|
+
"test('unchanged title', () => { verifyShared(); });\n"
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'citation removal is not an assertion edit');
|
|
76
|
+
assertUnknownCitation(result, [A], 'removed identity remains visible while the association is unresolved');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
checkCase('R3 adding B to an existing {A} citation keeps both candidate IDs under unknown scope', () => {
|
|
80
|
+
const result = derive(
|
|
81
|
+
`test('${A} title', () => { verifyShared(); });\n`,
|
|
82
|
+
`test('${A} ${B} title', () => { verifyShared(); });\n`
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
assertUnknownCitation(result, [A, B], 'the full candidate set is preserved after an identity-set change');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
checkCase('R3 reordering an unchanged REQ identity set remains a display-only title edit', () => {
|
|
89
|
+
const result = derive(
|
|
90
|
+
`test('${A} ${B} stable title', () => { verifyShared(); });\n`,
|
|
91
|
+
`test('${B} ${A} stable title', () => { verifyShared(); });\n`
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'the cited set is unchanged');
|
|
95
|
+
assert.strictEqual(result.facts.requirementScopeUnknown, false, 'sorted set identity remains stable');
|
|
96
|
+
assert.deepStrictEqual(result.facts.citedReqs, [], 'a display-only ID reordering adds no candidate change');
|
|
97
|
+
assert.strictEqual(result.requirementSync.applicability, 'N/A', 'unchanged citation identity stays N/A');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
checkCase('R3 multiple same-body tests can reorder by citation identity without cross-pairing', () => {
|
|
101
|
+
const result = derive(
|
|
102
|
+
[
|
|
103
|
+
`test('${A} first', () => { verifyShared(); });`,
|
|
104
|
+
`test('${B} second', () => { verifyShared(); });`
|
|
105
|
+
].join('\n'),
|
|
106
|
+
[
|
|
107
|
+
`test('${B} second', () => { verifyShared(); });`,
|
|
108
|
+
`test('${A} first', () => { verifyShared(); });`
|
|
109
|
+
].join('\n')
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
assert.strictEqual(result.facts.requirementScopeUnknown, false, 'same-body calls match by their unchanged REQ sets');
|
|
113
|
+
assert.strictEqual(result.requirementSync.applicability, 'N/A', 'reordering unchanged calls is not a citation change');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
checkCase('R3 shared-body A-to-C retarget stays unknown beside an unchanged B call', () => {
|
|
117
|
+
const result = derive(
|
|
118
|
+
[
|
|
119
|
+
`test('${A} first', () => { verifyShared(); });`,
|
|
120
|
+
`test('${B} second', () => { verifyShared(); });`
|
|
121
|
+
].join('\n'),
|
|
122
|
+
[
|
|
123
|
+
`test('${C} renamed first', () => { verifyShared(); });`,
|
|
124
|
+
`test('${B} second', () => { verifyShared(); });`
|
|
125
|
+
].join('\n')
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
assertUnknownCitation(result, [C], 'the shared body cannot consume A and hide candidate C');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
checkCase('R3 retargeting a legacy file-level REQ tag keeps candidate B and unknown scope', () => {
|
|
132
|
+
const result = derive(
|
|
133
|
+
`// ${A}\nassert.equal(run(), 1);\n`,
|
|
134
|
+
`// ${B}\nassert.equal(run(), 1);\n`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
assertUnknownCitation(result, [B], 'file-level citation retargeting is not a harmless comment edit');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
checkCase('R3 adding a legacy file-level REQ tag to an unchanged assertion is an identity delta', () => {
|
|
141
|
+
const result = derive(
|
|
142
|
+
'assert.equal(run(), 1);\n',
|
|
143
|
+
`// ${A}\nassert.equal(run(), 1);\n`
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'tag addition alone is not a new test or assertion edit');
|
|
147
|
+
assertUnknownCitation(result, [A], 'a newly attached legacy tag remains an unresolved candidate identity');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
checkCase('R3 removing a legacy file-level REQ tag keeps its old identity visible under unknown scope', () => {
|
|
151
|
+
const result = derive(
|
|
152
|
+
`// ${A}\nassert.equal(run(), 1);\n`,
|
|
153
|
+
'assert.equal(run(), 1);\n'
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, null, 'tag removal alone is not a new test or assertion edit');
|
|
157
|
+
assertUnknownCitation(result, [A], 'removed legacy identity and affected path remain visible');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
checkCase('R3 ordinary comment typo remains outside REQ citation identity', () => {
|
|
161
|
+
const result = derive(
|
|
162
|
+
'// baseline note\nassert.equal(run(), 1);\n',
|
|
163
|
+
'// basline note\nassert.equal(run(), 1);\n'
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
assert.strictEqual(result.facts.requirementScopeUnknown, false, 'ordinary comments do not create citation ambiguity');
|
|
167
|
+
assert.deepStrictEqual(result.facts.citedReqs, [], 'ordinary comments contribute no cited ID');
|
|
168
|
+
assert.strictEqual(result.requirementSync.applicability, 'N/A', 'ordinary comment typo stays N/A');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
checkCase('R3 genuine new REQ test remains a new-test signal', () => {
|
|
172
|
+
const result = derive(
|
|
173
|
+
`test('${A} existing', () => { verifyA(); });\n`,
|
|
174
|
+
[
|
|
175
|
+
`test('${A} existing', () => { verifyA(); });`,
|
|
176
|
+
`test('${B} added', verifyB);`
|
|
177
|
+
].join('\n')
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, 'new-test', 'a real test addition remains a new-test signal');
|
|
181
|
+
assert.deepStrictEqual(result.facts.citedReqs.map(item => item.id), [B], 'the genuinely added candidate ID is retained');
|
|
182
|
+
assert.strictEqual(result.requirementSync.applicability, 'applicable', 'a genuine test addition remains applicable');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
checkCase('R3 same-citation assertion change remains attributed and applicable', () => {
|
|
186
|
+
const result = derive(
|
|
187
|
+
`test('${A} stable title', () => { assert.equal(run(), 1); });\n`,
|
|
188
|
+
`test('${A} stable title', () => { assert.equal(run(), 2); });\n`
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
assert.strictEqual(result.facts.requirementSyncTrigger, 'assertion-change', 'an existing assertion edit keeps its trigger');
|
|
192
|
+
assert.deepStrictEqual(result.facts.citedReqs.map(item => item.id), [A], 'the changed assertion retains its existing citation');
|
|
193
|
+
assert.strictEqual(result.requirementSync.applicability, 'applicable', 'known same-REQ assertion edits remain applicable');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
checkCase('R3 ambiguous same-REQ body replacement remains unknown and keeps candidate identity', () => {
|
|
197
|
+
const result = derive(
|
|
198
|
+
`test('${A} old', () => { verifyOld(); });\n`,
|
|
199
|
+
`test('${A} replacement', () => { verifyNew(); });\n`
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
assertUnknownCitation(result, [A], 'an unresolved call association retains its candidate REQ');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
helper.finish();
|