@deftai/directive-core 0.99.0 → 0.101.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/dist/authz/actions.d.ts +6 -0
- package/dist/authz/actions.js +3 -0
- package/dist/authz/classify.js +241 -30
- package/dist/authz/decompose-apply.d.ts +81 -0
- package/dist/authz/decompose-apply.js +377 -0
- package/dist/authz/index.d.ts +3 -1
- package/dist/authz/index.js +3 -1
- package/dist/authz/store.d.ts +53 -0
- package/dist/authz/store.js +219 -1
- package/dist/authz/types.d.ts +19 -2
- package/dist/authz/types.js +5 -1
- package/dist/check/cached-orchestrator.d.ts +26 -5
- package/dist/check/cached-orchestrator.js +228 -6
- package/dist/check/gate-lists.d.ts +10 -4
- package/dist/check/gate-lists.js +19 -5
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +2 -1
- package/dist/check/named-cause.d.ts +45 -0
- package/dist/check/named-cause.js +121 -0
- package/dist/check/orchestrator.js +6 -1
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/help.d.ts +5 -0
- package/dist/doctor/help.js +31 -0
- package/dist/doctor/index.d.ts +2 -0
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +130 -79
- package/dist/doctor/session-coda.d.ts +82 -0
- package/dist/doctor/session-coda.js +151 -0
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/finish-loop/pr-finish-loop.d.ts +13 -2
- package/dist/finish-loop/pr-finish-loop.js +116 -5
- package/dist/finish-loop/types.d.ts +1 -1
- package/dist/hooks/dispatcher.d.ts +9 -5
- package/dist/hooks/dispatcher.js +23 -15
- package/dist/hooks/readonly.d.ts +6 -3
- package/dist/hooks/readonly.js +44 -25
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/init-deposit/gitignore.js +2 -0
- package/dist/intake/issue-ingest.js +36 -0
- package/dist/lifecycle/completed-consistency.d.ts +60 -0
- package/dist/lifecycle/completed-consistency.js +443 -0
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +47 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +415 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +2 -0
- package/dist/literal-acceptance/capture.d.ts +52 -0
- package/dist/literal-acceptance/capture.js +658 -0
- package/dist/literal-acceptance/evaluate.d.ts +47 -0
- package/dist/literal-acceptance/evaluate.js +193 -0
- package/dist/literal-acceptance/index.d.ts +12 -0
- package/dist/literal-acceptance/index.js +12 -0
- package/dist/literal-acceptance/run.d.ts +38 -0
- package/dist/literal-acceptance/run.js +216 -0
- package/dist/literal-acceptance/safety.d.ts +24 -0
- package/dist/literal-acceptance/safety.js +251 -0
- package/dist/literal-acceptance/types.d.ts +82 -0
- package/dist/literal-acceptance/types.js +26 -0
- package/dist/policy/ac-pass-banking.d.ts +58 -0
- package/dist/policy/ac-pass-banking.js +150 -0
- package/dist/policy/ceremony-dial.d.ts +96 -11
- package/dist/policy/ceremony-dial.js +250 -23
- package/dist/policy/index.d.ts +2 -0
- package/dist/policy/index.js +16 -1
- package/dist/policy/merge-approval-head.d.ts +129 -0
- package/dist/policy/merge-approval-head.js +461 -0
- package/dist/policy/require-human-merge.d.ts +5 -0
- package/dist/policy/require-human-merge.js +5 -0
- package/dist/pr-merge-readiness/ci-gate.d.ts +20 -3
- package/dist/pr-merge-readiness/ci-gate.js +65 -5
- package/dist/pr-merge-readiness/compute.d.ts +13 -0
- package/dist/pr-merge-readiness/compute.js +99 -14
- package/dist/pr-merge-readiness/gh.d.ts +49 -0
- package/dist/pr-merge-readiness/gh.js +268 -0
- package/dist/pr-merge-readiness/index.d.ts +2 -2
- package/dist/pr-merge-readiness/index.js +2 -2
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +11 -0
- package/dist/pr-wait-mergeable/cascade.js +115 -1
- package/dist/pr-wait-mergeable/main.d.ts +4 -0
- package/dist/pr-wait-mergeable/main.js +4 -0
- package/dist/pr-wait-mergeable/types.d.ts +3 -1
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -1
- package/dist/pr-wait-mergeable/wrappers.js +7 -1
- package/dist/preflight/evaluate.d.ts +15 -1
- package/dist/preflight/evaluate.js +33 -2
- package/dist/preflight-cache/evaluate.d.ts +3 -0
- package/dist/preflight-cache/evaluate.js +3 -0
- package/dist/product-first-done-gate/acceptance.d.ts +25 -0
- package/dist/product-first-done-gate/acceptance.js +247 -0
- package/dist/product-first-done-gate/check-mode.d.ts +45 -0
- package/dist/product-first-done-gate/check-mode.js +148 -0
- package/dist/product-first-done-gate/evaluate.d.ts +50 -0
- package/dist/product-first-done-gate/evaluate.js +255 -0
- package/dist/product-first-done-gate/index.d.ts +11 -0
- package/dist/product-first-done-gate/index.js +11 -0
- package/dist/product-first-done-gate/types.d.ts +64 -0
- package/dist/product-first-done-gate/types.js +61 -0
- package/dist/render/framework-commands.js +4 -0
- package/dist/run-summary/emit.d.ts +56 -0
- package/dist/run-summary/emit.js +171 -0
- package/dist/run-summary/index.d.ts +4 -0
- package/dist/run-summary/index.js +4 -0
- package/dist/run-summary/path.d.ts +25 -0
- package/dist/run-summary/path.js +78 -0
- package/dist/run-summary/types.d.ts +76 -0
- package/dist/run-summary/types.js +21 -0
- package/dist/scope/acceptance-evidence.d.ts +110 -0
- package/dist/scope/acceptance-evidence.js +422 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.d.ts +5 -0
- package/dist/scope/decompose.js +134 -11
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.js +3 -0
- package/dist/scope/parent-lineage.d.ts +125 -0
- package/dist/scope/parent-lineage.js +626 -0
- package/dist/scope/transition.d.ts +8 -0
- package/dist/scope/transition.js +61 -2
- package/dist/session/ac-pass-banking.d.ts +221 -0
- package/dist/session/ac-pass-banking.js +761 -0
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/deposit-sha.d.ts +49 -0
- package/dist/session/deposit-sha.js +121 -0
- package/dist/session/effort-budget.d.ts +136 -0
- package/dist/session/effort-budget.js +352 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +7 -0
- package/dist/session/orientation-compression.d.ts +127 -0
- package/dist/session/orientation-compression.js +425 -0
- package/dist/session/orientation-state.d.ts +29 -0
- package/dist/session/orientation-state.js +85 -0
- package/dist/session/session-start.d.ts +31 -0
- package/dist/session/session-start.js +228 -2
- package/dist/session/toolchain-preflight.d.ts +64 -0
- package/dist/session/toolchain-preflight.js +151 -0
- package/dist/session/verify-session-ritual.d.ts +14 -0
- package/dist/session/verify-session-ritual.js +33 -0
- package/dist/story-ready/evaluate.d.ts +11 -0
- package/dist/story-ready/evaluate.js +41 -3
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/triage/bootstrap/gitignore.d.ts +1 -1
- package/dist/triage/bootstrap/gitignore.js +8 -2
- package/dist/triage/bootstrap/index.js +15 -3
- package/dist/triage/classify/index.d.ts +1 -0
- package/dist/triage/classify/index.js +2 -0
- package/dist/triage/classify/label-mirror.js +16 -0
- package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
- package/dist/triage/classify/mirror-discovery-tip.js +234 -0
- package/dist/triage/reconcile/reconcile.js +28 -6
- package/dist/triage/summary/index.js +12 -4
- package/dist/triage/welcome/default-mode.js +16 -0
- package/dist/ts-check-lane/run-lane.js +21 -4
- package/dist/vbrief-validate/conformance.js +7 -0
- package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +15 -3
package/dist/scope/decompose.js
CHANGED
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
* A caller supplies a draft with child story definitions; this module validates
|
|
7
7
|
* that draft, writes the child story vBRIEFs, and updates the parent scope references.
|
|
8
8
|
*/
|
|
9
|
-
import { accessSync, constants, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
9
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
10
10
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
11
11
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
12
|
+
import { evaluateDecomposeStructuralApply, sha256Hex } from "../authz/decompose-apply.js";
|
|
13
|
+
import { claimSingleUseGrantForApply } from "../authz/store.js";
|
|
12
14
|
import { containedWrite } from "../fs/contained-write.js";
|
|
13
15
|
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
14
16
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
17
|
+
import { resolveRepo } from "../triage/queue/repo.js";
|
|
15
18
|
import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
|
|
16
19
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
20
|
+
import { formatCoverageReportLine, validateCoverageMap } from "./coverage-map.js";
|
|
21
|
+
import { buildParentLineageArtifact } from "./parent-lineage.js";
|
|
17
22
|
import { formatBriefJson } from "./vbrief-json.js";
|
|
18
23
|
// ---------------------------------------------------------------------------
|
|
19
24
|
// Constants
|
|
@@ -35,6 +40,13 @@ export class DecompositionError extends Error {
|
|
|
35
40
|
// File I/O helpers
|
|
36
41
|
// ---------------------------------------------------------------------------
|
|
37
42
|
function loadJson(path) {
|
|
43
|
+
return loadJsonWithRaw(path).data;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Single-read load: parse + return exact on-disk bytes used for #3239 digest binding.
|
|
47
|
+
* Avoids TOCTOU between validation snapshot and authz hash.
|
|
48
|
+
*/
|
|
49
|
+
function loadJsonWithRaw(path) {
|
|
38
50
|
let raw;
|
|
39
51
|
try {
|
|
40
52
|
raw = readFileSync(path, "utf8");
|
|
@@ -52,7 +64,7 @@ function loadJson(path) {
|
|
|
52
64
|
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
53
65
|
throw new DecompositionError(`${path}: expected a JSON object`);
|
|
54
66
|
}
|
|
55
|
-
return data;
|
|
67
|
+
return { data: data, raw };
|
|
56
68
|
}
|
|
57
69
|
function writeJson(projectRoot, path, data) {
|
|
58
70
|
// #2980 wave D: product write sink routes through containedWrite.
|
|
@@ -684,6 +696,10 @@ export function validateDraft(stories) {
|
|
|
684
696
|
if (parallelSafe !== true && parallelSafe !== false) {
|
|
685
697
|
issues.push("plan.metadata.swarm.parallel_safe");
|
|
686
698
|
}
|
|
699
|
+
// Parity with swarm:readiness (#3252): large work cannot be concurrent.
|
|
700
|
+
if (sw.size === "large" && parallelSafe === true) {
|
|
701
|
+
issues.push("size=large cannot be parallel_safe=true");
|
|
702
|
+
}
|
|
687
703
|
if (items.length === 0)
|
|
688
704
|
issues.push("plan.items");
|
|
689
705
|
if (items.length > 0 && !itemsHaveAcceptance(items)) {
|
|
@@ -777,7 +793,7 @@ function childFilename(story, stId, title, date) {
|
|
|
777
793
|
return `${date}-${sl}.vbrief.json`;
|
|
778
794
|
}
|
|
779
795
|
function buildChildVbrief(opts) {
|
|
780
|
-
const { story, storyId: stId, storyIndex, parent, parentRel, status } = opts;
|
|
796
|
+
const { story, storyId: stId, storyIndex, parent, parentRel, status, parentLineage } = opts;
|
|
781
797
|
const title = String(story.title ?? stId);
|
|
782
798
|
const sw = swarmMeta(story);
|
|
783
799
|
const items = itemsFromStory(stId, story);
|
|
@@ -786,6 +802,9 @@ function buildChildVbrief(opts) {
|
|
|
786
802
|
: {};
|
|
787
803
|
metadata.kind = "story";
|
|
788
804
|
metadata.swarm = sw;
|
|
805
|
+
if (parentLineage !== undefined && parentLineage !== null) {
|
|
806
|
+
metadata.parent_lineage = parentLineage;
|
|
807
|
+
}
|
|
789
808
|
const parentPlan = typeof parent.plan === "object" && parent.plan !== null && !Array.isArray(parent.plan)
|
|
790
809
|
? parent.plan
|
|
791
810
|
: {};
|
|
@@ -810,11 +829,22 @@ function buildChildVbrief(opts) {
|
|
|
810
829
|
}
|
|
811
830
|
export function applyDecomposition(opts) {
|
|
812
831
|
const { projectRoot, parentPath, draftPath, checkOnly, date } = opts;
|
|
832
|
+
// Repo identity for structural apply grants that pin scope.repo (#3291).
|
|
833
|
+
const explicitRepo = (opts.repo ?? "").trim();
|
|
834
|
+
const repo = explicitRepo.length > 0 ? explicitRepo : resolveRepo(null, projectRoot);
|
|
813
835
|
const vbriefDirPath = vbriefDir(projectRoot);
|
|
814
836
|
const parent = loadJson(parentPath);
|
|
815
|
-
|
|
837
|
+
// Single draft read: digest must bind the exact bytes used to build child docs (#3239 P1).
|
|
838
|
+
const { data: draft, raw: draftRaw } = loadJsonWithRaw(draftPath);
|
|
839
|
+
const draftDigest = sha256Hex(draftRaw);
|
|
816
840
|
const stories = storySpecs(draft);
|
|
817
841
|
const stIds = validateDraft(stories);
|
|
842
|
+
// #3238: semantic fidelity — parent requirement IDs + coverage map (structure only).
|
|
843
|
+
const coverage = validateCoverageMap({ parent, draft, storyIds: stIds });
|
|
844
|
+
if (!coverage.ok) {
|
|
845
|
+
const summary = coverage.errors.join("; ");
|
|
846
|
+
throw new DecompositionError(`semantic coverage failed: ${summary}\n${formatCoverageReportLine(coverage.report)}`);
|
|
847
|
+
}
|
|
818
848
|
let outputDir;
|
|
819
849
|
const draftOutputDir = draft.output_dir;
|
|
820
850
|
if (typeof draftOutputDir === "string" && draftOutputDir.trim().length > 0) {
|
|
@@ -843,6 +873,35 @@ export function applyDecomposition(opts) {
|
|
|
843
873
|
}
|
|
844
874
|
const parentRel = relToVbrief(vbriefDirPath, parentPath);
|
|
845
875
|
const actions = [`VALIDATED ${stories.length} story decomposition draft`];
|
|
876
|
+
// Machine-readable coverage report (always emit when parent authored IDs or map present).
|
|
877
|
+
if (coverage.report.parent_requirement_ids.length > 0 || coverage.report.entries.length > 0) {
|
|
878
|
+
actions.push(formatCoverageReportLine(coverage.report));
|
|
879
|
+
}
|
|
880
|
+
// #3241: stamp full parent coverage artifacts onto every child so story-ready /
|
|
881
|
+
// preflight can re-check lineage without re-reading the decompose draft.
|
|
882
|
+
// When parent authors requirement IDs, durable parent_plan_id is mandatory —
|
|
883
|
+
// omit/skip would let an exact-path replacement pass identity-unchecked.
|
|
884
|
+
const draftRec = draft;
|
|
885
|
+
const parentPlanRec = typeof parent.plan === "object" && parent.plan !== null && !Array.isArray(parent.plan)
|
|
886
|
+
? parent.plan
|
|
887
|
+
: {};
|
|
888
|
+
const parentPlanId = typeof parentPlanRec.id === "string" && parentPlanRec.id.trim().length > 0
|
|
889
|
+
? parentPlanRec.id.trim()
|
|
890
|
+
: undefined;
|
|
891
|
+
let parentLineageArtifact = null;
|
|
892
|
+
if (coverage.report.parent_requirement_ids.length > 0) {
|
|
893
|
+
if (parentPlanId === undefined) {
|
|
894
|
+
throw new DecompositionError("parent authors requirement IDs but has no non-empty plan.id — " +
|
|
895
|
+
"refuse decompose without durable parent identity for parent_plan_id stamp (#3241)");
|
|
896
|
+
}
|
|
897
|
+
parentLineageArtifact = buildParentLineageArtifact({
|
|
898
|
+
coverage_map: draftRec.coverage_map ?? draftRec.coverageMap,
|
|
899
|
+
behavioral_deltas: draftRec.behavioral_deltas ?? draftRec.behavioralDeltas,
|
|
900
|
+
parent_requirement_ids: coverage.report.parent_requirement_ids,
|
|
901
|
+
negative_invariant_ids: coverage.report.negative_invariant_ids,
|
|
902
|
+
parent_plan_id: parentPlanId,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
846
905
|
const childPaths = [];
|
|
847
906
|
const childDocs = [];
|
|
848
907
|
for (let i = 0; i < stories.length; i += 1) {
|
|
@@ -865,6 +924,7 @@ export function applyDecomposition(opts) {
|
|
|
865
924
|
parent,
|
|
866
925
|
parentRel,
|
|
867
926
|
status: storyStatus,
|
|
927
|
+
parentLineage: parentLineageArtifact,
|
|
868
928
|
});
|
|
869
929
|
childPaths.push({ target, storyId: stId, title });
|
|
870
930
|
childDocs.push(child);
|
|
@@ -873,13 +933,22 @@ export function applyDecomposition(opts) {
|
|
|
873
933
|
}
|
|
874
934
|
if (checkOnly)
|
|
875
935
|
return actions;
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
936
|
+
// #3239 / #3291: structural apply requires human-origin grant bound to exact draft digest
|
|
937
|
+
// from the single load above. Thread repo into both evaluator calls so repo-bound grants
|
|
938
|
+
// match. --check is ungated (returned earlier).
|
|
939
|
+
const authz = evaluateDecomposeStructuralApply({
|
|
940
|
+
projectRoot,
|
|
941
|
+
parentPath,
|
|
942
|
+
draftPath,
|
|
943
|
+
draftDigest,
|
|
944
|
+
repo,
|
|
945
|
+
});
|
|
946
|
+
if (!authz.allowed) {
|
|
947
|
+
throw new DecompositionError(authz.reason);
|
|
882
948
|
}
|
|
949
|
+
actions.push(`AUTHZ grant=${authz.humanApprovalRef ?? "unknown"} digest=${draftDigest.slice(0, 12)}`);
|
|
950
|
+
// Parent mutation validation before claim/writes so invalid parents do not
|
|
951
|
+
// spend a single-use grant (#3239).
|
|
883
952
|
let parentPlan = parent.plan;
|
|
884
953
|
if (parentPlan === null || parentPlan === undefined) {
|
|
885
954
|
parentPlan = {};
|
|
@@ -918,7 +987,61 @@ export function applyDecomposition(opts) {
|
|
|
918
987
|
planObj.references = dedupeReferences(references
|
|
919
988
|
.filter((r) => typeof r === "object" && r !== null && !Array.isArray(r))
|
|
920
989
|
.map((r) => r));
|
|
921
|
-
|
|
990
|
+
// Exclusive claim: revalidate under lock → mark single-use usedAt → write all
|
|
991
|
+
// children + parent; apply throw rolls usedAt back for retry (#3239 residual).
|
|
992
|
+
// Partial child files created in this attempt are best-effort removed so retry
|
|
993
|
+
// is not blocked by "child already exists".
|
|
994
|
+
const writeProtected = () => {
|
|
995
|
+
const created = [];
|
|
996
|
+
try {
|
|
997
|
+
for (const { target } of childPaths) {
|
|
998
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
999
|
+
}
|
|
1000
|
+
for (let i = 0; i < childPaths.length; i += 1) {
|
|
1001
|
+
// biome-ignore lint/style/noNonNullAssertion: loop bound ensures these exist
|
|
1002
|
+
const target = childPaths[i].target;
|
|
1003
|
+
// biome-ignore lint/style/noNonNullAssertion: loop bound ensures these exist
|
|
1004
|
+
writeJson(projectRoot, target, childDocs[i]);
|
|
1005
|
+
created.push(target);
|
|
1006
|
+
}
|
|
1007
|
+
writeJson(projectRoot, parentPath, parent);
|
|
1008
|
+
}
|
|
1009
|
+
catch (err) {
|
|
1010
|
+
for (const path of created.reverse()) {
|
|
1011
|
+
try {
|
|
1012
|
+
rmSync(path, { force: true });
|
|
1013
|
+
}
|
|
1014
|
+
catch {
|
|
1015
|
+
/* best-effort rollback of this attempt's children */
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
throw err;
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
if (authz.humanApprovalRef !== null) {
|
|
1022
|
+
const claim = claimSingleUseGrantForApply(projectRoot, authz.humanApprovalRef, {
|
|
1023
|
+
revalidate: (grant) => {
|
|
1024
|
+
// Same repo binding as the initial check (#3291) — under-lock revalidation must not
|
|
1025
|
+
// silently drop project identity or a repo-bound grant would fail closed incorrectly.
|
|
1026
|
+
const again = evaluateDecomposeStructuralApply({
|
|
1027
|
+
projectRoot,
|
|
1028
|
+
parentPath,
|
|
1029
|
+
draftPath,
|
|
1030
|
+
draftDigest,
|
|
1031
|
+
grants: [grant],
|
|
1032
|
+
repo,
|
|
1033
|
+
});
|
|
1034
|
+
return again.allowed ? { ok: true } : { ok: false, reason: again.reason };
|
|
1035
|
+
},
|
|
1036
|
+
apply: writeProtected,
|
|
1037
|
+
});
|
|
1038
|
+
if (!claim.ok) {
|
|
1039
|
+
throw new DecompositionError(claim.reason);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
else {
|
|
1043
|
+
writeProtected();
|
|
1044
|
+
}
|
|
922
1045
|
actions.push(`UPDATE ${parentRel} references`);
|
|
923
1046
|
return actions;
|
|
924
1047
|
}
|
package/dist/scope/index.d.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
export * from "./acceptance-evidence.js";
|
|
1
2
|
export * from "./audit-log.js";
|
|
2
3
|
export * from "./batch-promote.js";
|
|
3
4
|
export * from "./constants.js";
|
|
5
|
+
export * from "./coverage-map.js";
|
|
4
6
|
export * from "./decomposed-refs.js";
|
|
5
7
|
export * from "./delivery-evidence.js";
|
|
6
8
|
export * from "./demote.js";
|
|
7
9
|
export * from "./effort-activate-gate.js";
|
|
8
10
|
export * from "./main.js";
|
|
9
11
|
export * from "./open-umbrella-warning.js";
|
|
12
|
+
export * from "./parent-lineage.js";
|
|
10
13
|
export * from "./project-context.js";
|
|
11
14
|
export * from "./promote-from-issue.js";
|
|
12
15
|
export * from "./promote-path.js";
|
package/dist/scope/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
export * from "./acceptance-evidence.js";
|
|
1
2
|
export * from "./audit-log.js";
|
|
2
3
|
export * from "./batch-promote.js";
|
|
3
4
|
export * from "./constants.js";
|
|
5
|
+
export * from "./coverage-map.js";
|
|
4
6
|
export * from "./decomposed-refs.js";
|
|
5
7
|
export * from "./delivery-evidence.js";
|
|
6
8
|
export * from "./demote.js";
|
|
7
9
|
export * from "./effort-activate-gate.js";
|
|
8
10
|
export * from "./main.js";
|
|
9
11
|
export * from "./open-umbrella-warning.js";
|
|
12
|
+
export * from "./parent-lineage.js";
|
|
10
13
|
export * from "./project-context.js";
|
|
11
14
|
export * from "./promote-from-issue.js";
|
|
12
15
|
export * from "./promote-path.js";
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-lineage gates for story-ready and pre-PR/preflight (#3241 / epic #3237).
|
|
3
|
+
*
|
|
4
|
+
* After #3238 coverage artifacts exist, a green child can still contradict parent
|
|
5
|
+
* invariants unless build/review gates re-check structure. This module:
|
|
6
|
+
* - resolves parent via plan.planRef (or plan references)
|
|
7
|
+
* - when parent authors requirement IDs, requires child coverage artifacts
|
|
8
|
+
* - reuses validateCoverageMap (structure-only; no LLM judgment)
|
|
9
|
+
* - classifies defects: child_spec | parent_child_drift
|
|
10
|
+
*
|
|
11
|
+
* Backward compatible: no parent link, or parent with zero requirement IDs → N/A pass.
|
|
12
|
+
*/
|
|
13
|
+
import { type CoverageReport } from "./coverage-map.js";
|
|
14
|
+
export declare const PARENT_LINEAGE_SCHEMA: "deft.scope.parent_lineage.v1";
|
|
15
|
+
/** Defect classes for gate output (#3241 AC: distinguish child-spec vs parent/child drift). */
|
|
16
|
+
export declare const LINEAGE_DEFECT_CLASSES: readonly ["child_spec", "parent_child_drift"];
|
|
17
|
+
export type LineageDefectClass = (typeof LINEAGE_DEFECT_CLASSES)[number];
|
|
18
|
+
export interface ParentLineageResult {
|
|
19
|
+
readonly ok: boolean;
|
|
20
|
+
/** False when lineage does not apply (no parent / parent has no requirement IDs). */
|
|
21
|
+
readonly applicable: boolean;
|
|
22
|
+
readonly defect_class: LineageDefectClass | null;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
readonly parent_path: string | null;
|
|
25
|
+
readonly parent_requirement_ids: readonly string[];
|
|
26
|
+
readonly negative_invariant_ids: readonly string[];
|
|
27
|
+
readonly coverage_report: CoverageReport | null;
|
|
28
|
+
readonly errors: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
type JsonObj = Record<string, unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Walk up from a child artifact path to find the lifecycle root (xbrief/ or vbrief/).
|
|
33
|
+
*/
|
|
34
|
+
export declare function findLifecycleRootFromArtifact(artifactPath: string): string | null;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve plan.planRef (or similar relative ref) against a lifecycle root.
|
|
37
|
+
* Rejects absolute paths and `..` traversal.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveParentPathFromRef(planRef: string, lifecycleRoot: string): {
|
|
40
|
+
path: string | null;
|
|
41
|
+
error: string | null;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Extract coverage draft surface from a child xBRIEF for validateCoverageMap.
|
|
45
|
+
* Prefer plan.metadata.parent_lineage, then plan.metadata, then plan/root.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractChildCoverageDraft(child: unknown): {
|
|
48
|
+
draft: JsonObj | null;
|
|
49
|
+
source: string | null;
|
|
50
|
+
hasCoverageMapKey: boolean;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build the parent_lineage metadata block stamped onto decomposed children (#3241).
|
|
54
|
+
*/
|
|
55
|
+
export declare function buildParentLineageArtifact(opts: {
|
|
56
|
+
coverage_map?: unknown;
|
|
57
|
+
behavioral_deltas?: unknown;
|
|
58
|
+
parent_requirement_ids?: readonly string[];
|
|
59
|
+
negative_invariant_ids?: readonly string[];
|
|
60
|
+
/** Stable parent plan.id for lifecycle-fallback identity (exact match). */
|
|
61
|
+
parent_plan_id?: string;
|
|
62
|
+
}): JsonObj;
|
|
63
|
+
/** Read plan.id from a loaded parent document (plan.id or root id). */
|
|
64
|
+
export declare function extractPlanId(doc: unknown): string | null;
|
|
65
|
+
/**
|
|
66
|
+
* Scan lifecycle folders for artifacts whose plan.id exactly equals parentPlanId.
|
|
67
|
+
* Returns every match (caller enforces uniqueness). Does not use basenames.
|
|
68
|
+
*/
|
|
69
|
+
export declare function findParentsByPlanId(lifecycleRoot: string, parentPlanId: string): Array<{
|
|
70
|
+
path: string;
|
|
71
|
+
data: JsonObj;
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Load parent for lineage checks.
|
|
75
|
+
*
|
|
76
|
+
* 1. Prefer the exact planRef path when it exists; require plan.id match when
|
|
77
|
+
* the child stamps parent_plan_id (path replacement with a different plan is rejected).
|
|
78
|
+
* 2. When the exact path is missing AND the child stamps parent_plan_id: recover by
|
|
79
|
+
* scanning lifecycle folders for plan.id === parent_plan_id and accepting ONLY
|
|
80
|
+
* when exactly one artifact matches. This handles lifecycle moves when child
|
|
81
|
+
* planRef rewrite lagged — not silent basename recovery of unrelated files.
|
|
82
|
+
* 3. Without a durable stamp, stale/missing planRef fails closed (no basename guess).
|
|
83
|
+
*/
|
|
84
|
+
export declare function loadParentExact(parentPath: string, opts?: {
|
|
85
|
+
expectedParentPlanId?: string | null;
|
|
86
|
+
lifecycleRoot?: string | null;
|
|
87
|
+
}): {
|
|
88
|
+
ok: true;
|
|
89
|
+
data: JsonObj;
|
|
90
|
+
path: string;
|
|
91
|
+
} | {
|
|
92
|
+
ok: false;
|
|
93
|
+
error: string;
|
|
94
|
+
path: string;
|
|
95
|
+
};
|
|
96
|
+
/** Stamped parent_plan_id from child plan.metadata.parent_lineage (if any). */
|
|
97
|
+
export declare function extractStampedParentPlanId(child: unknown): string | null;
|
|
98
|
+
/**
|
|
99
|
+
* Pure parent-lineage evaluation for an in-memory child + optional parent.
|
|
100
|
+
* When parent is omitted, uses parentPath / project layout from options.
|
|
101
|
+
*/
|
|
102
|
+
export declare function evaluateParentLineage(opts: {
|
|
103
|
+
child: unknown;
|
|
104
|
+
/** Absolute path to the child artifact (used to locate lifecycle root). */
|
|
105
|
+
childPath?: string;
|
|
106
|
+
/** Pre-loaded parent document (skips disk load). */
|
|
107
|
+
parent?: unknown;
|
|
108
|
+
/** Explicit absolute parent path (also used for messaging). */
|
|
109
|
+
parentPath?: string;
|
|
110
|
+
/** Project root for resolving planRef when childPath is absent. */
|
|
111
|
+
projectRoot?: string;
|
|
112
|
+
/** When true, skip lineage (tests / opt-out). Default false. */
|
|
113
|
+
skip?: boolean;
|
|
114
|
+
}): ParentLineageResult;
|
|
115
|
+
/**
|
|
116
|
+
* Convenience: load child from path and evaluate lineage.
|
|
117
|
+
*/
|
|
118
|
+
export declare function evaluateParentLineageAtPath(childPath: string, options?: {
|
|
119
|
+
projectRoot?: string;
|
|
120
|
+
skip?: boolean;
|
|
121
|
+
}): ParentLineageResult;
|
|
122
|
+
/** One-line machine-readable lineage summary for gate stdout. */
|
|
123
|
+
export declare function formatParentLineageLine(result: ParentLineageResult): string;
|
|
124
|
+
export {};
|
|
125
|
+
//# sourceMappingURL=parent-lineage.d.ts.map
|