@deftai/directive-core 0.98.1 → 0.100.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 +401 -75
- package/dist/authz/decompose-apply.d.ts +67 -0
- package/dist/authz/decompose-apply.js +302 -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/consumer-check-contract/evaluate.d.ts +40 -0
- package/dist/consumer-check-contract/evaluate.js +188 -3
- package/dist/consumer-check-contract/index.d.ts +1 -1
- package/dist/consumer-check-contract/index.js +1 -1
- package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
- package/dist/content-contracts/skills/greptile-detector.js +202 -4
- package/dist/decision/index.d.ts +17 -0
- package/dist/decision/index.js +35 -0
- package/dist/decision/list.d.ts +47 -0
- package/dist/decision/list.js +250 -0
- package/dist/decision/schema.d.ts +88 -0
- package/dist/decision/schema.js +293 -0
- package/dist/decision/write.d.ts +82 -0
- package/dist/decision/write.js +427 -0
- package/dist/doctor/checks.d.ts +44 -0
- package/dist/doctor/checks.js +428 -2
- package/dist/doctor/main.d.ts +6 -7
- package/dist/doctor/main.js +72 -79
- package/dist/doctor/which.d.ts +20 -1
- package/dist/doctor/which.js +67 -1
- package/dist/eval/report.d.ts +29 -0
- package/dist/eval/report.js +69 -0
- package/dist/eval/run.d.ts +9 -0
- package/dist/eval/run.js +40 -4
- package/dist/eval/version-pin.d.ts +99 -0
- package/dist/eval/version-pin.js +181 -0
- 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 +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +20 -0
- package/dist/init-deposit/agent-hooks.js +51 -31
- package/dist/intake/issue-ingest.js +29 -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/platform/host-content-surface.d.ts +74 -0
- package/dist/platform/host-content-surface.js +214 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/ceremony-dial.d.ts +289 -0
- package/dist/policy/ceremony-dial.js +980 -0
- package/dist/policy/deft-directive-disable.js +12 -2
- 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/evaluate.js +10 -0
- 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/mergeability.js +5 -0
- package/dist/pr-merge-readiness/output.js +2 -0
- package/dist/pr-merge-readiness/parse.js +4 -0
- package/dist/pr-merge-readiness/test-gh-fixtures.helpers.js +4 -0
- package/dist/pr-merge-readiness/types.d.ts +6 -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/render/framework-commands.js +4 -0
- package/dist/scope/acceptance-evidence.d.ts +76 -0
- package/dist/scope/acceptance-evidence.js +348 -0
- package/dist/scope/coverage-map.d.ts +98 -0
- package/dist/scope/coverage-map.js +558 -0
- package/dist/scope/decompose.js +125 -11
- package/dist/scope/effort-activate-gate.d.ts +28 -0
- package/dist/scope/effort-activate-gate.js +64 -0
- package/dist/scope/index.d.ts +4 -0
- package/dist/scope/index.js +4 -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 +69 -2
- package/dist/session/active-cli.d.ts +79 -0
- package/dist/session/active-cli.js +382 -0
- package/dist/session/effort-budget.d.ts +130 -0
- package/dist/session/effort-budget.js +334 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/session-start.d.ts +30 -1
- package/dist/session/session-start.js +234 -26
- 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/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
- package/dist/swarm/pre-dispatch-cli.js +143 -0
- package/dist/swarm/pre-dispatch.d.ts +87 -0
- package/dist/swarm/pre-dispatch.js +373 -0
- package/dist/triage/actions/candidates-log.d.ts +1 -2
- package/dist/triage/actions/candidates-log.js +37 -6
- package/dist/vbrief-activate/activate.js +6 -0
- package/dist/vbrief-validate/constants.d.ts +2 -0
- package/dist/vbrief-validate/constants.js +2 -0
- package/dist/vbrief-validate/schema.js +4 -1
- package/dist/xbrief-migrate/migrate-project.d.ts +4 -0
- package/dist/xbrief-migrate/migrate-project.js +92 -3
- package/package.json +19 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname } from "node:path";
|
|
3
3
|
import { evaluateIntentCeilingFromEnv } from "../policy/intent-ceiling.js";
|
|
4
|
+
import { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
|
|
4
5
|
/** Canonical eligibility folder — only vbrief/active/ may spawn implementation. */
|
|
5
6
|
export const ACTIVE_FOLDER = "active";
|
|
6
7
|
/** Canonical eligibility status — only `running` signals an active handoff. */
|
|
@@ -37,8 +38,12 @@ function nodeJsonErrorToPythonMsg(nodeMessage) {
|
|
|
37
38
|
* Pure evaluator — returns `{ exitCode, message }`. Never throws; every error
|
|
38
39
|
* path collapses to exit 1 with an actionable message. Faithful to
|
|
39
40
|
* `scripts/preflight_implementation.py::evaluate`.
|
|
41
|
+
*
|
|
42
|
+
* #3241: after active+running + intent ceiling, re-check parent requirement
|
|
43
|
+
* lineage (coverage + approved behavioral deltas). Fail closed on missing
|
|
44
|
+
* coverage or undeclared deltas when the parent authors requirement IDs.
|
|
40
45
|
*/
|
|
41
|
-
export function evaluate(vbriefPath) {
|
|
46
|
+
export function evaluate(vbriefPath, options = {}) {
|
|
42
47
|
const path = vbriefPath;
|
|
43
48
|
let st;
|
|
44
49
|
try {
|
|
@@ -134,11 +139,37 @@ export function evaluate(vbriefPath) {
|
|
|
134
139
|
message: buildReject(path, intent.reason),
|
|
135
140
|
};
|
|
136
141
|
}
|
|
142
|
+
// #3241 pre-PR / implementation preflight: parent lineage fail-closed.
|
|
143
|
+
const lineage = evaluateParentLineage({
|
|
144
|
+
child: record,
|
|
145
|
+
childPath: path,
|
|
146
|
+
projectRoot: options.projectRoot,
|
|
147
|
+
skip: options.skipParentLineage === true,
|
|
148
|
+
});
|
|
149
|
+
if (!lineage.ok) {
|
|
150
|
+
const defect = lineage.defect_class !== null ? ` [defect_class=${lineage.defect_class}]` : "";
|
|
151
|
+
return {
|
|
152
|
+
exitCode: 1,
|
|
153
|
+
parentLineage: lineage,
|
|
154
|
+
message: buildReject(path, `${lineage.message}${defect}\n ${formatParentLineageLine(lineage)}`),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Keep the historical OK line when lineage is N/A (backward-compatible tests / agents).
|
|
158
|
+
const message = lineage.applicable
|
|
159
|
+
? `OK ${path} -- ready for implementation. parent lineage OK ` +
|
|
160
|
+
`(${lineage.parent_requirement_ids.length} req IDs` +
|
|
161
|
+
(lineage.negative_invariant_ids.length > 0
|
|
162
|
+
? `, ${lineage.negative_invariant_ids.length} negative invariants`
|
|
163
|
+
: "") +
|
|
164
|
+
`).`
|
|
165
|
+
: `OK ${path} -- ready for implementation.`;
|
|
137
166
|
return {
|
|
138
167
|
exitCode: 0,
|
|
139
|
-
|
|
168
|
+
parentLineage: lineage,
|
|
169
|
+
message,
|
|
140
170
|
};
|
|
141
171
|
}
|
|
172
|
+
export { evaluateParentLineage, formatParentLineageLine, } from "../scope/parent-lineage.js";
|
|
142
173
|
/** Structured `--json` payload (sorted keys), mirroring Python `_emit_json`. */
|
|
143
174
|
export function emitJson(vbriefPath, exitCode, message) {
|
|
144
175
|
const payload = {
|
|
@@ -85,6 +85,9 @@ export const COMMANDS = {
|
|
|
85
85
|
"verify:orphan-active": spec("verify:orphan-active", "verify_orphan_active:main", {
|
|
86
86
|
projectRootArg: "--project-root",
|
|
87
87
|
}),
|
|
88
|
+
"verify:completed-tracked": spec("verify:completed-tracked", "verify_completed_tracked:main", {
|
|
89
|
+
projectRootArg: "--project-root",
|
|
90
|
+
}),
|
|
88
91
|
"verify:pack-drift": spec("verify:pack-drift", "pack_render:main", {
|
|
89
92
|
defaultArgs: ["--check"],
|
|
90
93
|
cwd: "framework",
|
|
@@ -299,6 +302,7 @@ const ENTRYPOINT_VERB = {
|
|
|
299
302
|
"preflight_cache:main": "preflight-cache",
|
|
300
303
|
"preflight_wip_cap:main": "verify-wip-cap",
|
|
301
304
|
"verify_orphan_active:main": "verify-orphan-active",
|
|
305
|
+
"verify_completed_tracked:main": "verify-completed-tracked",
|
|
302
306
|
"pack_render:main": "pack-render",
|
|
303
307
|
"validate_strategy_output:main": "validate-strategy-output",
|
|
304
308
|
"vbrief_validate:main": "vbrief-validate",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-acceptance-criterion evidence / disposition for scope:complete (#3240 / epic #3237 Q3).
|
|
3
|
+
*
|
|
4
|
+
* Merge ancestry alone must not auto-complete acceptance items. Each non-terminal
|
|
5
|
+
* plan item needs either typed evidence or a human-origin disposition before complete
|
|
6
|
+
* may advance it. Kind suitability: merge/review alone cannot satisfy smoke, UAT,
|
|
7
|
+
* deploy, or observed_behavior requirements.
|
|
8
|
+
*/
|
|
9
|
+
import type { GrantOrigin } from "../authz/types.js";
|
|
10
|
+
/** Closed evidence kinds (locked Q3). */
|
|
11
|
+
export declare const ACCEPTANCE_EVIDENCE_KINDS: readonly ["test", "review", "merge", "deploy", "smoke", "uat", "observed_behavior"];
|
|
12
|
+
export type AcceptanceEvidenceKind = (typeof ACCEPTANCE_EVIDENCE_KINDS)[number];
|
|
13
|
+
/** Closed dispositions (locked Q3). */
|
|
14
|
+
export declare const ACCEPTANCE_DISPOSITIONS: readonly ["waived", "deferred", "not_applicable"];
|
|
15
|
+
export type AcceptanceDispositionKind = (typeof ACCEPTANCE_DISPOSITIONS)[number];
|
|
16
|
+
/**
|
|
17
|
+
* Axes that merge/review evidence alone cannot satisfy.
|
|
18
|
+
* Matches epic #3237 Q3 suitability rule.
|
|
19
|
+
*/
|
|
20
|
+
export declare const STRICT_ACCEPTANCE_AXES: readonly ["deploy", "smoke", "uat", "observed_behavior"];
|
|
21
|
+
export type StrictAcceptanceAxis = (typeof STRICT_ACCEPTANCE_AXES)[number];
|
|
22
|
+
export interface AcceptanceEvidenceRecord {
|
|
23
|
+
readonly kind: AcceptanceEvidenceKind;
|
|
24
|
+
readonly pointer: string;
|
|
25
|
+
readonly recorded_at: string;
|
|
26
|
+
readonly recorded_by: string;
|
|
27
|
+
}
|
|
28
|
+
export interface AcceptanceDispositionRecord {
|
|
29
|
+
readonly disposition: AcceptanceDispositionKind;
|
|
30
|
+
readonly reason: string;
|
|
31
|
+
/** Human-origin provenance (kind + actor required; other GrantOrigin fields optional). */
|
|
32
|
+
readonly provenance: Pick<GrantOrigin, "kind" | "actor"> & Partial<GrantOrigin>;
|
|
33
|
+
readonly recorded_at: string;
|
|
34
|
+
readonly resume_when?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CriterionAcceptanceReport {
|
|
37
|
+
readonly path: string;
|
|
38
|
+
readonly title: string;
|
|
39
|
+
readonly outcome: "evidence" | "disposition" | "already_terminal" | "missing" | "invalid";
|
|
40
|
+
readonly detail: string;
|
|
41
|
+
readonly evidence?: AcceptanceEvidenceRecord;
|
|
42
|
+
readonly disposition?: AcceptanceDispositionRecord;
|
|
43
|
+
}
|
|
44
|
+
export interface AcceptanceEvidenceGateResult {
|
|
45
|
+
readonly ok: boolean;
|
|
46
|
+
readonly message: string;
|
|
47
|
+
readonly reports: readonly CriterionAcceptanceReport[];
|
|
48
|
+
}
|
|
49
|
+
export declare function isAcceptanceEvidenceKind(value: unknown): value is AcceptanceEvidenceKind;
|
|
50
|
+
export declare function isAcceptanceDispositionKind(value: unknown): value is AcceptanceDispositionKind;
|
|
51
|
+
/**
|
|
52
|
+
* Infer strict axes a criterion requires from explicit fields and title/Acceptance text.
|
|
53
|
+
* Explicit `requires` / `requiredEvidenceKind` / `acceptanceAxis` wins when valid.
|
|
54
|
+
*/
|
|
55
|
+
export declare function inferRequiredStrictAxes(item: Record<string, unknown>): StrictAcceptanceAxis[];
|
|
56
|
+
/**
|
|
57
|
+
* Whether evidence.kind is suitable for the criterion's required strict axes.
|
|
58
|
+
* merge and review never satisfy smoke/UAT/deploy/observed_behavior.
|
|
59
|
+
*
|
|
60
|
+
* A single evidence.kind covers only that axis. When free-text inference yields
|
|
61
|
+
* multiple axes (e.g. "smoke after deploy"), every required axis must match the
|
|
62
|
+
* same kind — which is only possible when the axes are identical. Otherwise use
|
|
63
|
+
* explicit `requires` / `acceptanceAxis` for a single axis, split the criterion,
|
|
64
|
+
* or record a human-origin disposition (#3240 Greptile P1).
|
|
65
|
+
*/
|
|
66
|
+
export declare function isEvidenceKindSuitable(kind: AcceptanceEvidenceKind, requiredAxes: readonly StrictAcceptanceAxis[]): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Fail closed when any non-terminal plan item lacks suitable evidence or a human-origin disposition.
|
|
69
|
+
*/
|
|
70
|
+
export declare function evaluateAcceptanceEvidenceGate(plan: Record<string, unknown>): AcceptanceEvidenceGateResult;
|
|
71
|
+
/**
|
|
72
|
+
* Format a short multi-line listing of evidence/disposition for every criterion
|
|
73
|
+
* (including already-terminal) for complete success output.
|
|
74
|
+
*/
|
|
75
|
+
export declare function formatAcceptanceCompletionListing(reports: readonly CriterionAcceptanceReport[]): string;
|
|
76
|
+
//# sourceMappingURL=acceptance-evidence.d.ts.map
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-acceptance-criterion evidence / disposition for scope:complete (#3240 / epic #3237 Q3).
|
|
3
|
+
*
|
|
4
|
+
* Merge ancestry alone must not auto-complete acceptance items. Each non-terminal
|
|
5
|
+
* plan item needs either typed evidence or a human-origin disposition before complete
|
|
6
|
+
* may advance it. Kind suitability: merge/review alone cannot satisfy smoke, UAT,
|
|
7
|
+
* deploy, or observed_behavior requirements.
|
|
8
|
+
*/
|
|
9
|
+
import { isHumanOrigin } from "../authz/origin.js";
|
|
10
|
+
/** Closed evidence kinds (locked Q3). */
|
|
11
|
+
export const ACCEPTANCE_EVIDENCE_KINDS = [
|
|
12
|
+
"test",
|
|
13
|
+
"review",
|
|
14
|
+
"merge",
|
|
15
|
+
"deploy",
|
|
16
|
+
"smoke",
|
|
17
|
+
"uat",
|
|
18
|
+
"observed_behavior",
|
|
19
|
+
];
|
|
20
|
+
/** Closed dispositions (locked Q3). */
|
|
21
|
+
export const ACCEPTANCE_DISPOSITIONS = ["waived", "deferred", "not_applicable"];
|
|
22
|
+
/**
|
|
23
|
+
* Axes that merge/review evidence alone cannot satisfy.
|
|
24
|
+
* Matches epic #3237 Q3 suitability rule.
|
|
25
|
+
*/
|
|
26
|
+
export const STRICT_ACCEPTANCE_AXES = ["deploy", "smoke", "uat", "observed_behavior"];
|
|
27
|
+
const EVIDENCE_KIND_SET = new Set(ACCEPTANCE_EVIDENCE_KINDS);
|
|
28
|
+
const DISPOSITION_SET = new Set(ACCEPTANCE_DISPOSITIONS);
|
|
29
|
+
const STRICT_AXIS_SET = new Set(STRICT_ACCEPTANCE_AXES);
|
|
30
|
+
/** Item statuses that still represent unfinished acceptance work (#2862 / #3240). */
|
|
31
|
+
const NON_TERMINAL_ITEM_STATUSES = new Set(["pending", "proposed", "running"]);
|
|
32
|
+
function asRecord(value) {
|
|
33
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
function isNonEmptyString(value) {
|
|
39
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
40
|
+
}
|
|
41
|
+
export function isAcceptanceEvidenceKind(value) {
|
|
42
|
+
return typeof value === "string" && EVIDENCE_KIND_SET.has(value.trim().toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
export function isAcceptanceDispositionKind(value) {
|
|
45
|
+
return typeof value === "string" && DISPOSITION_SET.has(value.trim().toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Infer strict axes a criterion requires from explicit fields and title/Acceptance text.
|
|
49
|
+
* Explicit `requires` / `requiredEvidenceKind` / `acceptanceAxis` wins when valid.
|
|
50
|
+
*/
|
|
51
|
+
export function inferRequiredStrictAxes(item) {
|
|
52
|
+
const explicit = [
|
|
53
|
+
item.requires,
|
|
54
|
+
item.requiredEvidenceKind,
|
|
55
|
+
item.required_evidence_kind,
|
|
56
|
+
item.acceptanceAxis,
|
|
57
|
+
item.acceptance_axis,
|
|
58
|
+
];
|
|
59
|
+
for (const raw of explicit) {
|
|
60
|
+
if (typeof raw !== "string")
|
|
61
|
+
continue;
|
|
62
|
+
const norm = raw.trim().toLowerCase().replace(/-/g, "_");
|
|
63
|
+
if (STRICT_AXIS_SET.has(norm)) {
|
|
64
|
+
return [norm];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const narrative = asRecord(item.narrative);
|
|
68
|
+
const parts = [];
|
|
69
|
+
if (typeof item.title === "string")
|
|
70
|
+
parts.push(item.title);
|
|
71
|
+
if (typeof item.id === "string")
|
|
72
|
+
parts.push(item.id);
|
|
73
|
+
if (narrative !== null) {
|
|
74
|
+
for (const key of ["Acceptance", "acceptance", "Description", "description", "Test", "test"]) {
|
|
75
|
+
if (typeof narrative[key] === "string")
|
|
76
|
+
parts.push(narrative[key]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const text = parts.join("\n").toLowerCase();
|
|
80
|
+
const found = [];
|
|
81
|
+
// Order: longer / more specific phrases first so "observed behavior" wins over generic.
|
|
82
|
+
if (/\bobserved[_\s-]?behavior\b/.test(text) ||
|
|
83
|
+
/\bobserved behaviour\b/.test(text) ||
|
|
84
|
+
/\brunning behaviour\b/.test(text) ||
|
|
85
|
+
/\brunning behavior\b/.test(text)) {
|
|
86
|
+
found.push("observed_behavior");
|
|
87
|
+
}
|
|
88
|
+
if (/\buat\b/.test(text) || /\buser acceptance\b/.test(text)) {
|
|
89
|
+
found.push("uat");
|
|
90
|
+
}
|
|
91
|
+
if (/\bsmoke\b/.test(text)) {
|
|
92
|
+
found.push("smoke");
|
|
93
|
+
}
|
|
94
|
+
if (/\bdeploy(?:ment|ed|s)?\b/.test(text)) {
|
|
95
|
+
found.push("deploy");
|
|
96
|
+
}
|
|
97
|
+
return found;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Whether evidence.kind is suitable for the criterion's required strict axes.
|
|
101
|
+
* merge and review never satisfy smoke/UAT/deploy/observed_behavior.
|
|
102
|
+
*
|
|
103
|
+
* A single evidence.kind covers only that axis. When free-text inference yields
|
|
104
|
+
* multiple axes (e.g. "smoke after deploy"), every required axis must match the
|
|
105
|
+
* same kind — which is only possible when the axes are identical. Otherwise use
|
|
106
|
+
* explicit `requires` / `acceptanceAxis` for a single axis, split the criterion,
|
|
107
|
+
* or record a human-origin disposition (#3240 Greptile P1).
|
|
108
|
+
*/
|
|
109
|
+
export function isEvidenceKindSuitable(kind, requiredAxes) {
|
|
110
|
+
if (requiredAxes.length === 0) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (kind === "merge" || kind === "review") {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
// All required strict axes must be covered by this single evidence kind.
|
|
117
|
+
return requiredAxes.every((axis) => kind === axis);
|
|
118
|
+
}
|
|
119
|
+
function parseEvidence(raw) {
|
|
120
|
+
const obj = asRecord(raw);
|
|
121
|
+
if (obj === null) {
|
|
122
|
+
return { ok: false, message: "evidence must be an object" };
|
|
123
|
+
}
|
|
124
|
+
const kindRaw = typeof obj.kind === "string" ? obj.kind.trim().toLowerCase() : "";
|
|
125
|
+
if (!isAcceptanceEvidenceKind(kindRaw)) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
message: `unknown or missing evidence.kind ${JSON.stringify(obj.kind)}; ` +
|
|
129
|
+
`allowed: ${ACCEPTANCE_EVIDENCE_KINDS.join("|")}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (!isNonEmptyString(obj.pointer)) {
|
|
133
|
+
return { ok: false, message: "evidence.pointer is required (non-empty string)" };
|
|
134
|
+
}
|
|
135
|
+
if (!isNonEmptyString(obj.recorded_at)) {
|
|
136
|
+
return { ok: false, message: "evidence.recorded_at is required" };
|
|
137
|
+
}
|
|
138
|
+
if (!isNonEmptyString(obj.recorded_by)) {
|
|
139
|
+
return { ok: false, message: "evidence.recorded_by is required" };
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: true,
|
|
143
|
+
record: {
|
|
144
|
+
kind: kindRaw,
|
|
145
|
+
pointer: obj.pointer.trim(),
|
|
146
|
+
recorded_at: obj.recorded_at.trim(),
|
|
147
|
+
recorded_by: obj.recorded_by.trim(),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
function parseDisposition(raw) {
|
|
152
|
+
const obj = asRecord(raw);
|
|
153
|
+
if (obj === null) {
|
|
154
|
+
return { ok: false, message: "disposition must be an object" };
|
|
155
|
+
}
|
|
156
|
+
const dispRaw = typeof obj.disposition === "string" ? obj.disposition.trim().toLowerCase() : "";
|
|
157
|
+
if (!isAcceptanceDispositionKind(dispRaw)) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
message: `unknown or missing disposition.disposition ${JSON.stringify(obj.disposition)}; ` +
|
|
161
|
+
`allowed: ${ACCEPTANCE_DISPOSITIONS.join("|")}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (!isNonEmptyString(obj.reason)) {
|
|
165
|
+
return { ok: false, message: "disposition.reason is required" };
|
|
166
|
+
}
|
|
167
|
+
if (!isNonEmptyString(obj.recorded_at)) {
|
|
168
|
+
return { ok: false, message: "disposition.recorded_at is required" };
|
|
169
|
+
}
|
|
170
|
+
const provenance = asRecord(obj.provenance);
|
|
171
|
+
if (provenance === null) {
|
|
172
|
+
return { ok: false, message: "disposition.provenance is required (human-origin)" };
|
|
173
|
+
}
|
|
174
|
+
// Reuse authz human-origin gate (#2944) so agent-self stamps cannot waive criteria.
|
|
175
|
+
const originForCheck = {
|
|
176
|
+
kind: String(provenance.kind ?? ""),
|
|
177
|
+
actor: String(provenance.actor ?? ""),
|
|
178
|
+
mintedAt: typeof provenance.mintedAt === "string" ? provenance.mintedAt : "",
|
|
179
|
+
mintedVia: typeof provenance.mintedVia === "string" ? provenance.mintedVia : "",
|
|
180
|
+
eventRef: typeof provenance.eventRef === "string" ? provenance.eventRef : null,
|
|
181
|
+
};
|
|
182
|
+
if (!isHumanOrigin(originForCheck)) {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
message: "disposition.provenance must be human-origin " +
|
|
186
|
+
"(kind operator-cli|operator-session|human-event + non-agent actor)",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const record = {
|
|
190
|
+
disposition: dispRaw,
|
|
191
|
+
reason: obj.reason.trim(),
|
|
192
|
+
provenance: {
|
|
193
|
+
kind: originForCheck.kind,
|
|
194
|
+
actor: originForCheck.actor,
|
|
195
|
+
...(originForCheck.mintedAt.length > 0 ? { mintedAt: originForCheck.mintedAt } : {}),
|
|
196
|
+
...(originForCheck.mintedVia.length > 0 ? { mintedVia: originForCheck.mintedVia } : {}),
|
|
197
|
+
...(originForCheck.eventRef !== null ? { eventRef: originForCheck.eventRef } : {}),
|
|
198
|
+
},
|
|
199
|
+
recorded_at: obj.recorded_at.trim(),
|
|
200
|
+
};
|
|
201
|
+
if (isNonEmptyString(obj.resume_when)) {
|
|
202
|
+
return {
|
|
203
|
+
ok: true,
|
|
204
|
+
record: { ...record, resume_when: obj.resume_when.trim() },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return { ok: true, record };
|
|
208
|
+
}
|
|
209
|
+
function itemLabel(item, path) {
|
|
210
|
+
if (typeof item.title === "string" && item.title.trim().length > 0) {
|
|
211
|
+
return item.title.trim();
|
|
212
|
+
}
|
|
213
|
+
if (typeof item.id === "string" && item.id.trim().length > 0) {
|
|
214
|
+
return item.id.trim();
|
|
215
|
+
}
|
|
216
|
+
return path;
|
|
217
|
+
}
|
|
218
|
+
function evaluateOneItem(item, path) {
|
|
219
|
+
const title = itemLabel(item, path);
|
|
220
|
+
const status = String(item.status ?? "");
|
|
221
|
+
if (!NON_TERMINAL_ITEM_STATUSES.has(status)) {
|
|
222
|
+
return {
|
|
223
|
+
path,
|
|
224
|
+
title,
|
|
225
|
+
outcome: "already_terminal",
|
|
226
|
+
detail: `status=${status || "(empty)"} (not advanced)`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const hasEvidence = item.evidence !== undefined && item.evidence !== null;
|
|
230
|
+
const hasDisposition = item.disposition !== undefined && item.disposition !== null;
|
|
231
|
+
if (hasEvidence && hasDisposition) {
|
|
232
|
+
return {
|
|
233
|
+
path,
|
|
234
|
+
title,
|
|
235
|
+
outcome: "invalid",
|
|
236
|
+
detail: "exactly one of evidence or disposition required; both present",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (!hasEvidence && !hasDisposition) {
|
|
240
|
+
return {
|
|
241
|
+
path,
|
|
242
|
+
title,
|
|
243
|
+
outcome: "missing",
|
|
244
|
+
detail: "no evidence or disposition; scope:complete refuses to auto-complete (#3240)",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (hasDisposition) {
|
|
248
|
+
const parsed = parseDisposition(item.disposition);
|
|
249
|
+
if (!parsed.ok) {
|
|
250
|
+
return { path, title, outcome: "invalid", detail: parsed.message };
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
path,
|
|
254
|
+
title,
|
|
255
|
+
outcome: "disposition",
|
|
256
|
+
detail: `${parsed.record.disposition}: ${parsed.record.reason}`,
|
|
257
|
+
disposition: parsed.record,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const parsed = parseEvidence(item.evidence);
|
|
261
|
+
if (!parsed.ok) {
|
|
262
|
+
return { path, title, outcome: "invalid", detail: parsed.message };
|
|
263
|
+
}
|
|
264
|
+
const requiredAxes = inferRequiredStrictAxes(item);
|
|
265
|
+
if (!isEvidenceKindSuitable(parsed.record.kind, requiredAxes)) {
|
|
266
|
+
const axes = requiredAxes.length > 0 ? requiredAxes.join("|") : "(none)";
|
|
267
|
+
return {
|
|
268
|
+
path,
|
|
269
|
+
title,
|
|
270
|
+
outcome: "invalid",
|
|
271
|
+
detail: `evidence.kind=${parsed.record.kind} is not suitable for required axis/axes [${axes}]; ` +
|
|
272
|
+
`merge/review alone cannot satisfy smoke|uat|deploy|observed_behavior (#3240)`,
|
|
273
|
+
evidence: parsed.record,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
path,
|
|
278
|
+
title,
|
|
279
|
+
outcome: "evidence",
|
|
280
|
+
detail: `${parsed.record.kind} @ ${parsed.record.pointer}`,
|
|
281
|
+
evidence: parsed.record,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function walkItems(items, pathPrefix, reports) {
|
|
285
|
+
if (!Array.isArray(items)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
items.forEach((item, index) => {
|
|
289
|
+
if (item === null || typeof item !== "object" || Array.isArray(item)) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const obj = item;
|
|
293
|
+
const path = `${pathPrefix}[${index}]`;
|
|
294
|
+
reports.push(evaluateOneItem(obj, path));
|
|
295
|
+
walkItems(obj.subItems, `${path}.subItems`, reports);
|
|
296
|
+
walkItems(obj.items, `${path}.items`, reports);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Fail closed when any non-terminal plan item lacks suitable evidence or a human-origin disposition.
|
|
301
|
+
*/
|
|
302
|
+
export function evaluateAcceptanceEvidenceGate(plan) {
|
|
303
|
+
const reports = [];
|
|
304
|
+
walkItems(plan.items, "items", reports);
|
|
305
|
+
const blockers = reports.filter((r) => r.outcome === "missing" || r.outcome === "invalid");
|
|
306
|
+
if (blockers.length === 0) {
|
|
307
|
+
const lines = reports
|
|
308
|
+
.filter((r) => r.outcome === "evidence" || r.outcome === "disposition")
|
|
309
|
+
.map((r) => ` - ${r.path} "${r.title}": ${r.outcome} (${r.detail})`);
|
|
310
|
+
const summary = lines.length > 0
|
|
311
|
+
? `Acceptance evidence gate passed (#3240):\n${lines.join("\n")}`
|
|
312
|
+
: "Acceptance evidence gate passed (#3240): no non-terminal criteria";
|
|
313
|
+
return { ok: true, message: summary, reports };
|
|
314
|
+
}
|
|
315
|
+
const lines = blockers.map((r) => ` - ${r.path} "${r.title}": ${r.detail}`);
|
|
316
|
+
return {
|
|
317
|
+
ok: false,
|
|
318
|
+
message: `Acceptance evidence required for scope:complete (#3240). ` +
|
|
319
|
+
`${blockers.length} criterion/criteria missing suitable evidence or disposition:\n` +
|
|
320
|
+
`${lines.join("\n")}\n` +
|
|
321
|
+
`Each non-terminal plan item needs evidence ` +
|
|
322
|
+
`{kind: test|review|merge|deploy|smoke|uat|observed_behavior, pointer, recorded_at, recorded_by} ` +
|
|
323
|
+
`or disposition {disposition: waived|deferred|not_applicable, reason, provenance (human-origin), recorded_at}. ` +
|
|
324
|
+
`merge/review alone cannot satisfy smoke|uat|deploy|observed_behavior criteria.`,
|
|
325
|
+
reports,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Format a short multi-line listing of evidence/disposition for every criterion
|
|
330
|
+
* (including already-terminal) for complete success output.
|
|
331
|
+
*/
|
|
332
|
+
export function formatAcceptanceCompletionListing(reports) {
|
|
333
|
+
if (reports.length === 0) {
|
|
334
|
+
return "Acceptance criteria: (none)";
|
|
335
|
+
}
|
|
336
|
+
const lines = reports.map((r) => {
|
|
337
|
+
if (r.outcome === "evidence" && r.evidence) {
|
|
338
|
+
return ` - ${r.path} "${r.title}": evidence kind=${r.evidence.kind} pointer=${r.evidence.pointer}`;
|
|
339
|
+
}
|
|
340
|
+
if (r.outcome === "disposition" && r.disposition) {
|
|
341
|
+
return (` - ${r.path} "${r.title}": disposition=${r.disposition.disposition} ` +
|
|
342
|
+
`reason=${r.disposition.reason}`);
|
|
343
|
+
}
|
|
344
|
+
return ` - ${r.path} "${r.title}": ${r.outcome} (${r.detail})`;
|
|
345
|
+
});
|
|
346
|
+
return `Acceptance criteria:\n${lines.join("\n")}`;
|
|
347
|
+
}
|
|
348
|
+
//# sourceMappingURL=acceptance-evidence.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parent-requirement coverage map for scope:decompose semantic fidelity (#3238 / epic #3237).
|
|
3
|
+
*
|
|
4
|
+
* When a parent scope authors stable requirement IDs, a decomposition draft must
|
|
5
|
+
* declare a machine-readable coverage map for every ID. Dispositions are a closed
|
|
6
|
+
* enum (covered | deferred | split | behavioral_delta) with required side fields.
|
|
7
|
+
* Negative invariants cannot be silently dropped: omit → fail closed; intentional
|
|
8
|
+
* change requires disposition behavioral_delta with a linked delta record.
|
|
9
|
+
*
|
|
10
|
+
* No LLM/prose semantic-equivalence judgment — structure only (Q1/Q7/Q8 locks).
|
|
11
|
+
*/
|
|
12
|
+
export declare const COVERAGE_DISPOSITIONS: readonly ["covered", "deferred", "split", "behavioral_delta"];
|
|
13
|
+
export type CoverageDisposition = (typeof COVERAGE_DISPOSITIONS)[number];
|
|
14
|
+
export declare const BEHAVIORAL_DELTA_CHANGE_KINDS: readonly ["reorder_stages", "remove_invariant", "weaken_invariant", "add_terminal_outcome", "remove_fallback", "change_fallback_order", "other"];
|
|
15
|
+
export type BehavioralDeltaChangeKind = (typeof BEHAVIORAL_DELTA_CHANGE_KINDS)[number];
|
|
16
|
+
export interface ParentRequirement {
|
|
17
|
+
readonly id: string;
|
|
18
|
+
readonly title: string;
|
|
19
|
+
readonly negativeInvariant: boolean;
|
|
20
|
+
readonly path: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DeferredProvenance {
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
readonly target_story_id?: string;
|
|
25
|
+
readonly target_path?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface CoverageMapEntry {
|
|
28
|
+
readonly parent_requirement_id: string;
|
|
29
|
+
readonly disposition: CoverageDisposition;
|
|
30
|
+
readonly child_story_ids?: readonly string[];
|
|
31
|
+
readonly provenance?: DeferredProvenance;
|
|
32
|
+
readonly split_group?: string;
|
|
33
|
+
readonly part?: string;
|
|
34
|
+
readonly delta_id?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface BehavioralDeltaRecord {
|
|
37
|
+
readonly delta_id: string;
|
|
38
|
+
readonly parent_requirement_ids: readonly string[];
|
|
39
|
+
readonly change_kind: BehavioralDeltaChangeKind;
|
|
40
|
+
readonly summary: string;
|
|
41
|
+
readonly before: string;
|
|
42
|
+
readonly after: string;
|
|
43
|
+
readonly rationale: string;
|
|
44
|
+
}
|
|
45
|
+
export interface CoverageEntryReport {
|
|
46
|
+
readonly parent_requirement_id: string;
|
|
47
|
+
readonly disposition: string | null;
|
|
48
|
+
readonly ok: boolean;
|
|
49
|
+
readonly detail: string;
|
|
50
|
+
readonly negative_invariant: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface CoverageReport {
|
|
53
|
+
readonly schema: "deft.decompose.coverage_report.v1";
|
|
54
|
+
readonly ok: boolean;
|
|
55
|
+
readonly parent_requirement_ids: readonly string[];
|
|
56
|
+
readonly negative_invariant_ids: readonly string[];
|
|
57
|
+
readonly entries: readonly CoverageEntryReport[];
|
|
58
|
+
readonly uncovered: readonly string[];
|
|
59
|
+
readonly errors: readonly string[];
|
|
60
|
+
}
|
|
61
|
+
export interface CoverageValidationResult {
|
|
62
|
+
readonly ok: boolean;
|
|
63
|
+
readonly report: CoverageReport;
|
|
64
|
+
readonly errors: readonly string[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Collect authored parent requirement IDs (Q1).
|
|
68
|
+
* Sources: plan.items (nested) with id; plan.requirements; plan.metadata.requirement_ids.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractParentRequirements(parent: unknown): ParentRequirement[];
|
|
71
|
+
/**
|
|
72
|
+
* Normalize draft.coverage_map from object-map or array form into entry list.
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseCoverageMap(raw: unknown): {
|
|
75
|
+
entries: CoverageMapEntry[];
|
|
76
|
+
errors: string[];
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Parse draft.behavioral_deltas (array). Records missing required fields are rejected.
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseBehavioralDeltas(raw: unknown): {
|
|
82
|
+
deltas: BehavioralDeltaRecord[];
|
|
83
|
+
errors: string[];
|
|
84
|
+
byId: Map<string, BehavioralDeltaRecord>;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Validate coverage map against parent requirements and optional story ids.
|
|
88
|
+
* When the parent authors zero requirement IDs, validation is a no-op success
|
|
89
|
+
* (backward compatible with pre-#3238 drafts).
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateCoverageMap(opts: {
|
|
92
|
+
parent: unknown;
|
|
93
|
+
draft: unknown;
|
|
94
|
+
storyIds?: readonly string[];
|
|
95
|
+
}): CoverageValidationResult;
|
|
96
|
+
/** Format a single-line machine-readable coverage report for CLI stdout. */
|
|
97
|
+
export declare function formatCoverageReportLine(report: CoverageReport): string;
|
|
98
|
+
//# sourceMappingURL=coverage-map.d.ts.map
|