@deftai/directive-core 0.106.0 → 0.108.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/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/gate-lists.js +2 -0
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/design-critique/exclusive-chip.d.ts +33 -0
- package/dist/design-critique/exclusive-chip.js +68 -0
- package/dist/design-critique/parent-audit.d.ts +59 -0
- package/dist/design-critique/parent-audit.js +121 -0
- package/dist/doctor/checks.d.ts +6 -0
- package/dist/doctor/checks.js +35 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dest-form.d.ts +46 -0
- package/dist/hooks/dest-form.js +575 -0
- package/dist/hooks/dispatcher.d.ts +69 -2
- package/dist/hooks/dispatcher.js +609 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/github-auth-modes-cli.js +14 -2
- package/dist/intake/github-auth-modes.d.ts +54 -9
- package/dist/intake/github-auth-modes.js +326 -123
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
- package/dist/lifecycle/completed-write-guard.d.ts +48 -0
- package/dist/lifecycle/completed-write-guard.js +373 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/literal-acceptance/capture.d.ts +16 -0
- package/dist/literal-acceptance/capture.js +75 -7
- package/dist/literal-acceptance/evaluate.js +25 -3
- package/dist/literal-acceptance/index.d.ts +2 -2
- package/dist/literal-acceptance/index.js +2 -2
- package/dist/literal-acceptance/run.js +9 -3
- package/dist/literal-acceptance/types.d.ts +6 -0
- package/dist/literal-acceptance/types.js +6 -0
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/runtime-authority.d.ts +18 -0
- package/dist/policy/runtime-authority.js +11 -0
- package/dist/policy/value-feedback.js +9 -18
- package/dist/policy/write-fence.js +3 -0
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/preflight-cache/evaluate.js +9 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +53 -12
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +38 -0
- package/dist/scm/design-critique-chip.js +187 -0
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +3 -0
- package/dist/scm/main.js +15 -1
- package/dist/scm/readiness-cli.d.ts +2 -0
- package/dist/scm/readiness-cli.js +42 -1
- package/dist/scm/readiness.d.ts +4 -1
- package/dist/scm/readiness.js +35 -5
- package/dist/scope/decompose.js +2 -1
- package/dist/scope/delivery-evidence.d.ts +27 -1
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/lifecycle-write.d.ts +24 -0
- package/dist/scope/lifecycle-write.js +64 -0
- package/dist/scope/main.js +3 -1
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/scope/transition.js +90 -11
- package/dist/scope-provenance/evaluate.js +5 -1
- package/dist/session/git.d.ts +42 -0
- package/dist/session/git.js +219 -3
- package/dist/session/occupancy.d.ts +239 -12
- package/dist/session/occupancy.js +820 -27
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +17 -1
- package/dist/session/session-start.js +49 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +33 -3
- package/dist/session/verify-session-ritual.js +89 -19
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +77 -0
- package/dist/swarm/launch.js +248 -38
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/actions/index.d.ts +4 -0
- package/dist/triage/actions/index.js +6 -0
- package/dist/triage/evaluate/evaluate.d.ts +11 -0
- package/dist/triage/evaluate/evaluate.js +169 -0
- package/dist/triage/evaluate/github.d.ts +16 -0
- package/dist/triage/evaluate/github.js +153 -0
- package/dist/triage/evaluate/index.d.ts +9 -0
- package/dist/triage/evaluate/index.js +8 -0
- package/dist/triage/evaluate/paths.d.ts +7 -0
- package/dist/triage/evaluate/paths.js +25 -0
- package/dist/triage/evaluate/sink.d.ts +5 -0
- package/dist/triage/evaluate/sink.js +52 -0
- package/dist/triage/evaluate/types.d.ts +93 -0
- package/dist/triage/evaluate/types.js +13 -0
- package/dist/triage/evaluate/validity.d.ts +8 -0
- package/dist/triage/evaluate/validity.js +87 -0
- package/dist/triage/evaluate/value.d.ts +12 -0
- package/dist/triage/evaluate/value.js +33 -0
- package/dist/triage/evaluate/wip-census.d.ts +5 -0
- package/dist/triage/evaluate/wip-census.js +39 -0
- package/dist/triage/evaluate/worktrees.d.ts +8 -0
- package/dist/triage/evaluate/worktrees.js +68 -0
- package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
- package/dist/triage/evaluate/xbrief-refs.js +60 -0
- package/dist/triage/help/registry-data.d.ts +21 -7
- package/dist/triage/help/registry-data.js +53 -5
- package/dist/triage/index.d.ts +1 -0
- package/dist/triage/index.js +1 -0
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/value/feedback-file.d.ts +17 -2
- package/dist/value/feedback-file.js +80 -6
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-reconcile/labels.d.ts +1 -0
- package/dist/vbrief-reconcile/labels.js +30 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/vbrief-validation/story-quality.d.ts +7 -0
- package/dist/vbrief-validation/story-quality.js +8 -1
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/dist/xbrief-migrate/agents-header.js +69 -7
- package/package.json +7 -3
|
@@ -9,20 +9,24 @@
|
|
|
9
9
|
* remedy without embedding env values.
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
13
13
|
import { allGatesCliDispatchable, GLOBAL_CLI_REMEDY, isCliNativeGate, } from "../check/cli-native-gates.js";
|
|
14
14
|
import { isFrameworkRepoRoot, isFrameworkSourceContext } from "../check/context.js";
|
|
15
15
|
import { CONSUMER_CHECK_GATES, checkGateId, FRAMEWORK_CHECK_GATES, } from "../check/gate-lists.js";
|
|
16
16
|
import { defaultWhich } from "../doctor/which.js";
|
|
17
|
+
import { packageManagerSourceLabel, resolveProjectPackageManager, } from "../resolution/package-manager.js";
|
|
18
|
+
/** Maintainer toolchain remains pnpm-based. Consumer tools are selected dynamically. */
|
|
17
19
|
export const TOOLCHAIN_PREFLIGHT_TOOLS = ["task", "pnpm", "node", "git"];
|
|
18
20
|
const REMEDY = {
|
|
19
21
|
task: "Install go-task: https://taskfile.dev/installation/ (e.g. winget install Task.Task / brew install go-task)",
|
|
22
|
+
npm: "Install or repair Node 20+ (npm is bundled), then re-run the consumer check",
|
|
20
23
|
pnpm: "Enable pnpm via corepack: corepack enable && corepack prepare pnpm@latest --activate",
|
|
21
|
-
node: "Install Node 20+ (see .nvmrc)
|
|
24
|
+
node: "Install Node 20+ (see .nvmrc)",
|
|
22
25
|
git: "Install Git: https://git-scm.com/downloads",
|
|
23
26
|
};
|
|
24
27
|
const CAUSE = {
|
|
25
28
|
task: "go-task binary not found on PATH",
|
|
29
|
+
npm: "npm binary not found on PATH",
|
|
26
30
|
pnpm: "pnpm binary not found on PATH",
|
|
27
31
|
node: "node binary not found on PATH",
|
|
28
32
|
git: "git binary not found on PATH",
|
|
@@ -32,20 +36,29 @@ const CAUSE = {
|
|
|
32
36
|
* When task/node is missing, the orchestrator skips *all* scheduled gates
|
|
33
37
|
* (dynamic from gate-lists), not this static list — avoids hardcode drift.
|
|
34
38
|
*/
|
|
35
|
-
export const
|
|
39
|
+
export const PACKAGE_MANAGER_DEPENDENT_GATE_IDS = [
|
|
36
40
|
"toolchain:check",
|
|
37
41
|
"toolchain:check-consumer",
|
|
38
42
|
"ts:check-lane",
|
|
39
43
|
];
|
|
44
|
+
/** @deprecated use PACKAGE_MANAGER_DEPENDENT_GATE_IDS. */
|
|
45
|
+
export const PNPM_DEPENDENT_GATE_IDS = PACKAGE_MANAGER_DEPENDENT_GATE_IDS;
|
|
40
46
|
/** Sentinel skip id meaning "every gate in the active check composition". */
|
|
41
47
|
export const SKIP_ALL_GATES = "*";
|
|
42
|
-
function probeTool(tool, which) {
|
|
48
|
+
function probeTool(tool, which, packageManager) {
|
|
43
49
|
const present = which(tool) !== null;
|
|
50
|
+
let remedy = REMEDY[tool];
|
|
51
|
+
if (tool === "node" && packageManager === "npm") {
|
|
52
|
+
remedy = "Install or repair Node 20+ (npm is bundled), then re-run the consumer check";
|
|
53
|
+
}
|
|
54
|
+
else if (tool === "node" && packageManager === "pnpm") {
|
|
55
|
+
remedy = "Install Node 20+ (see .nvmrc); then enable pnpm with Corepack";
|
|
56
|
+
}
|
|
44
57
|
return {
|
|
45
58
|
tool,
|
|
46
59
|
present,
|
|
47
60
|
cause: present ? null : CAUSE[tool],
|
|
48
|
-
remedy: present ? null :
|
|
61
|
+
remedy: present ? null : remedy,
|
|
49
62
|
};
|
|
50
63
|
}
|
|
51
64
|
function probeCliDist(frameworkRoot, which, exists) {
|
|
@@ -78,7 +91,8 @@ function formatFindingLine(finding) {
|
|
|
78
91
|
}
|
|
79
92
|
if (finding.impact === "none") {
|
|
80
93
|
const cause = finding.cause ?? "absent";
|
|
81
|
-
|
|
94
|
+
const remedy = finding.remedy === null ? "" : `; remedy: ${finding.remedy}`;
|
|
95
|
+
return `[deft preflight] ${finding.tool}: absent (impact: none) — ${cause}${remedy}`;
|
|
82
96
|
}
|
|
83
97
|
const cause = finding.cause ?? "missing";
|
|
84
98
|
const remedy = finding.remedy ?? "install the tool";
|
|
@@ -90,14 +104,17 @@ function inferConsumerDeposit(options) {
|
|
|
90
104
|
const project = options.projectRoot;
|
|
91
105
|
if (project === undefined)
|
|
92
106
|
return false;
|
|
93
|
-
// Same-path roots are framework-source only when the tree is the source repo.
|
|
94
|
-
if (isFrameworkRepoRoot(project))
|
|
95
|
-
return false;
|
|
96
107
|
const framework = options.frameworkRoot ?? project;
|
|
108
|
+
// session:start passes equal roots for both framework source and a consumer
|
|
109
|
+
// deposit. Equal roots identify maintainer mode only when the tree has the
|
|
110
|
+
// framework-source markers; otherwise this is the production consumer shape.
|
|
111
|
+
if (resolve(framework) === resolve(project)) {
|
|
112
|
+
return !isFrameworkRepoRoot(project);
|
|
113
|
+
}
|
|
97
114
|
return !isFrameworkSourceContext(framework, project);
|
|
98
115
|
}
|
|
99
116
|
/**
|
|
100
|
-
* Run done-gate toolchain preflight.
|
|
117
|
+
* Run done-gate toolchain preflight. Read-only probe — never installs or builds.
|
|
101
118
|
*/
|
|
102
119
|
export function runToolchainPreflight(options = {}) {
|
|
103
120
|
const which = options.which ?? defaultWhich;
|
|
@@ -106,16 +123,44 @@ export function runToolchainPreflight(options = {}) {
|
|
|
106
123
|
const composed = options.composedGates ?? (consumerDeposit ? CONSUMER_CHECK_GATES : FRAMEWORK_CHECK_GATES);
|
|
107
124
|
const allCli = allGatesCliDispatchable(composed);
|
|
108
125
|
const findings = [];
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
let packageManager = "pnpm";
|
|
127
|
+
let packageManagerSource = "maintainer";
|
|
128
|
+
let packageManagerSelectionFailed = false;
|
|
129
|
+
let selectedTools = TOOLCHAIN_PREFLIGHT_TOOLS;
|
|
130
|
+
if (consumerDeposit) {
|
|
131
|
+
const resolution = resolveProjectPackageManager({
|
|
132
|
+
projectRoot: options.projectRoot,
|
|
133
|
+
env: options.env,
|
|
134
|
+
});
|
|
135
|
+
if (resolution.ok) {
|
|
136
|
+
packageManager = resolution.packageManager;
|
|
137
|
+
packageManagerSource = resolution.source;
|
|
138
|
+
selectedTools = ["task", packageManager, "node", "git"];
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
packageManager = null;
|
|
142
|
+
packageManagerSource = null;
|
|
143
|
+
packageManagerSelectionFailed = true;
|
|
144
|
+
selectedTools = ["task", "node", "git"];
|
|
145
|
+
findings.push({
|
|
146
|
+
tool: "package_manager",
|
|
147
|
+
present: false,
|
|
148
|
+
cause: resolution.message,
|
|
149
|
+
remedy: "Set package.json#packageManager (or DEFT_PACKAGE_MANAGER) to a supported npm or pnpm value",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const tool of selectedTools) {
|
|
154
|
+
findings.push(probeTool(tool, which, packageManager));
|
|
111
155
|
}
|
|
112
156
|
if (options.probeCliDist !== false) {
|
|
113
157
|
findings.push(probeCliDist(options.frameworkRoot ?? options.projectRoot, which, exists));
|
|
114
158
|
}
|
|
115
159
|
const cliPresent = findings.some((f) => f.tool === "cli_dist" && f.present);
|
|
116
160
|
const taskMissing = findings.some((f) => f.tool === "task" && !f.present);
|
|
117
|
-
const pnpmMissing = findings.some((f) => f.tool === "pnpm" && !f.present);
|
|
118
161
|
const nodeMissing = findings.some((f) => f.tool === "node" && !f.present);
|
|
162
|
+
const packageManagerMissing = packageManagerSelectionFailed ||
|
|
163
|
+
(packageManager !== null && findings.some((f) => f.tool === packageManager && !f.present));
|
|
119
164
|
// #3335: in a deposit whose composed gates are all CLI-dispatchable, a
|
|
120
165
|
// missing task binary is impact none — no install-go-task remedy.
|
|
121
166
|
if (taskMissing && allCli) {
|
|
@@ -132,10 +177,30 @@ export function runToolchainPreflight(options = {}) {
|
|
|
132
177
|
};
|
|
133
178
|
}
|
|
134
179
|
}
|
|
180
|
+
// Keep finding impact aligned with the skip calculation. Git is advisory in
|
|
181
|
+
// this preflight, and a local CLI dist matters only when task is absent and
|
|
182
|
+
// every composed gate must dispatch through the CLI.
|
|
183
|
+
for (let index = 0; index < findings.length; index += 1) {
|
|
184
|
+
const finding = findings[index];
|
|
185
|
+
if (finding === undefined || finding.present)
|
|
186
|
+
continue;
|
|
187
|
+
if (finding.tool === "git") {
|
|
188
|
+
findings[index] = { ...finding, impact: "none" };
|
|
189
|
+
}
|
|
190
|
+
else if (finding.tool === "cli_dist") {
|
|
191
|
+
findings[index] = {
|
|
192
|
+
...finding,
|
|
193
|
+
impact: taskMissing && allCli && !cliPresent ? "degraded" : "none",
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
135
197
|
const missingCritical = findings.filter((f) => {
|
|
136
198
|
if (f.present || f.impact === "none")
|
|
137
199
|
return false;
|
|
138
|
-
return f.tool === "task" ||
|
|
200
|
+
return (f.tool === "task" ||
|
|
201
|
+
f.tool === "node" ||
|
|
202
|
+
f.tool === "package_manager" ||
|
|
203
|
+
f.tool === packageManager);
|
|
139
204
|
});
|
|
140
205
|
const skip = new Set();
|
|
141
206
|
if (nodeMissing) {
|
|
@@ -156,8 +221,8 @@ export function runToolchainPreflight(options = {}) {
|
|
|
156
221
|
else if (taskMissing && allCli && !cliPresent) {
|
|
157
222
|
skip.add(SKIP_ALL_GATES);
|
|
158
223
|
}
|
|
159
|
-
else if (
|
|
160
|
-
for (const id of
|
|
224
|
+
else if (packageManagerMissing) {
|
|
225
|
+
for (const id of PACKAGE_MANAGER_DEPENDENT_GATE_IDS) {
|
|
161
226
|
skip.add(id);
|
|
162
227
|
}
|
|
163
228
|
}
|
|
@@ -166,6 +231,12 @@ export function runToolchainPreflight(options = {}) {
|
|
|
166
231
|
const status = ok ? "ok" : "degraded";
|
|
167
232
|
const lines = [];
|
|
168
233
|
lines.push(`[deft preflight] toolchain status: ${status}`);
|
|
234
|
+
if (consumerDeposit &&
|
|
235
|
+
packageManager !== null &&
|
|
236
|
+
packageManagerSource !== null &&
|
|
237
|
+
packageManagerSource !== "maintainer") {
|
|
238
|
+
lines.push(`[deft preflight] package manager: ${packageManager} (${packageManagerSourceLabel(packageManagerSource)})`);
|
|
239
|
+
}
|
|
169
240
|
for (const finding of findings) {
|
|
170
241
|
if (!finding.present) {
|
|
171
242
|
lines.push(formatFindingLine(finding));
|
|
@@ -173,8 +244,8 @@ export function runToolchainPreflight(options = {}) {
|
|
|
173
244
|
}
|
|
174
245
|
if (ok) {
|
|
175
246
|
lines.push(taskMissing && allCli
|
|
176
|
-
?
|
|
177
|
-
:
|
|
247
|
+
? `[deft preflight] done-gate toolchain ready (CLI dispatch; go-task not required; package manager: ${packageManager}) (#3335)`
|
|
248
|
+
: `[deft preflight] done-gate toolchain ready (task, ${packageManager}, node)`);
|
|
178
249
|
}
|
|
179
250
|
else {
|
|
180
251
|
lines.push("[deft preflight] degraded mode: directive check will skip toolchain-dependent gates " +
|
|
@@ -190,6 +261,8 @@ export function runToolchainPreflight(options = {}) {
|
|
|
190
261
|
findings,
|
|
191
262
|
lines,
|
|
192
263
|
skipGateIds: [...skip].sort(),
|
|
264
|
+
packageManager,
|
|
265
|
+
packageManagerSource,
|
|
193
266
|
};
|
|
194
267
|
}
|
|
195
268
|
/** Serialize preflight for run-summary / ritual-state (no env values). */
|
|
@@ -198,6 +271,8 @@ export function toolchainPreflightToDict(result) {
|
|
|
198
271
|
status: result.status,
|
|
199
272
|
ok: result.ok,
|
|
200
273
|
degraded: result.degraded,
|
|
274
|
+
package_manager: result.packageManager ?? null,
|
|
275
|
+
package_manager_source: result.packageManagerSource ?? null,
|
|
201
276
|
findings: result.findings.map((f) => ({
|
|
202
277
|
tool: f.tool,
|
|
203
278
|
present: f.present,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ActiveCliCheckResult, type ActiveCliCheckSeams } from "./active-cli.js";
|
|
2
2
|
import { type GitRunner } from "./git.js";
|
|
3
3
|
import { type DirectivePosture } from "./posture.js";
|
|
4
|
-
import { type GatedStepName, type SessionCeremonyTier } from "./session-start.js";
|
|
4
|
+
import { type GatedStepName, type SessionCeremonyTier, WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS } from "./session-start.js";
|
|
5
5
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
6
|
+
export { WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS };
|
|
6
7
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
7
8
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
8
9
|
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<GatedStepName, readonly string[]>>;
|
|
@@ -40,6 +41,11 @@ export interface VerifyResult {
|
|
|
40
41
|
* `rearm` when age/compact stale on an otherwise valid bind; `cold` otherwise.
|
|
41
42
|
*/
|
|
42
43
|
readonly recoveryTier?: SessionCeremonyTier | null;
|
|
44
|
+
/**
|
|
45
|
+
* Internal owner from the exact ritual state evaluated for this verdict.
|
|
46
|
+
* Absent until worktree/HEAD binding succeeds; never projected by emitVerifyJson.
|
|
47
|
+
*/
|
|
48
|
+
readonly boundSessionId?: string;
|
|
43
49
|
}
|
|
44
50
|
export type RitualRunner = (command: readonly string[], projectRoot: string) => {
|
|
45
51
|
code: number;
|
|
@@ -81,7 +87,24 @@ export interface VerifySessionRitualOptions {
|
|
|
81
87
|
readonly checkActiveCli?: (targetVersion: string | null, seams?: ActiveCliCheckSeams) => ActiveCliCheckResult;
|
|
82
88
|
/** Injectable work-selection detector for gated `cache_fresh` (#3507). */
|
|
83
89
|
readonly detectWorkSelection?: DetectWorkSelection;
|
|
90
|
+
/**
|
|
91
|
+
* Required gated steps for this surface. Defaults to all {@link GATED_STEPS}
|
|
92
|
+
* (session / full gated verify). Write dispatch uses
|
|
93
|
+
* {@link WRITE_GATED_REQUIRED_STEPS} (#3738).
|
|
94
|
+
*/
|
|
95
|
+
readonly requiredGatedSteps?: readonly GatedStepName[];
|
|
96
|
+
/**
|
|
97
|
+
* Gated steps this surface may execute. Defaults to all {@link GATED_STEPS}.
|
|
98
|
+
* Write dispatch uses {@link WRITE_GATED_EXECUTE_STEPS} so `cache_fresh` and
|
|
99
|
+
* `doctor` cannot run as write-path repair (#3738).
|
|
100
|
+
*/
|
|
101
|
+
readonly executeGatedSteps?: readonly GatedStepName[];
|
|
84
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Options for the write/spawn mutation verifier (#3738).
|
|
105
|
+
* Required set omits `cache_fresh`; execution authority is `agent_hooks` only.
|
|
106
|
+
*/
|
|
107
|
+
export declare function writeGateRitualOptions(extras?: Omit<VerifySessionRitualOptions, "requiredGatedSteps" | "executeGatedSteps" | "tier">): VerifySessionRitualOptions;
|
|
85
108
|
export interface InspectSessionRitualOptions {
|
|
86
109
|
readonly tier?: "quick" | "gated";
|
|
87
110
|
readonly now?: Date;
|
|
@@ -89,13 +112,20 @@ export interface InspectSessionRitualOptions {
|
|
|
89
112
|
readonly posture?: DirectivePosture;
|
|
90
113
|
readonly envPosture?: string | undefined;
|
|
91
114
|
readonly handoffText?: string | null;
|
|
115
|
+
/**
|
|
116
|
+
* Required gated steps for this surface. Defaults to all {@link GATED_STEPS}.
|
|
117
|
+
* Write dispatch passes {@link WRITE_GATED_REQUIRED_STEPS} so a no-write
|
|
118
|
+
* inspect reports the same readiness its verifying counterpart would (#3738).
|
|
119
|
+
*/
|
|
120
|
+
readonly requiredGatedSteps?: readonly GatedStepName[];
|
|
92
121
|
}
|
|
93
122
|
/**
|
|
94
123
|
* Read-only ritual-state inspection for host hooks.
|
|
95
124
|
*
|
|
96
125
|
* Unlike {@link verifySessionRitual}, this never runs missing gated entrypoints
|
|
97
|
-
* and never rewrites `.deft/ritual-state.json`.
|
|
98
|
-
*
|
|
126
|
+
* and never rewrites `.deft/ritual-state.json`. Write dispatch does **not** use
|
|
127
|
+
* this function: it must still execute `agent_hooks` (#3738). A PreToolUse
|
|
128
|
+
* decision must not become a hidden `doctor` / cache-refresh mutation boundary.
|
|
99
129
|
*/
|
|
100
130
|
export declare function inspectSessionRitual(projectRoot: string, options?: InspectSessionRitualOptions): VerifyResult;
|
|
101
131
|
export declare function verifySessionRitual(projectRoot: string, options?: VerifySessionRitualOptions): VerifyResult;
|
|
@@ -10,9 +10,10 @@ import { pythonJsonDump } from "./json.js";
|
|
|
10
10
|
import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
|
|
11
11
|
import { defaultRitualRunner } from "./ritual-entrypoint.js";
|
|
12
12
|
import { readRitualState, ritualStateMarksRearmNeeded, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
13
|
-
import { formatSessionStartRecoveryCommand, GATED_STEPS, QUICK_STEPS, } from "./session-start.js";
|
|
13
|
+
import { formatSessionStartRecoveryCommand, GATED_STEPS, QUICK_STEPS, WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS, } from "./session-start.js";
|
|
14
14
|
import { resolveSessionRitualStalenessHours } from "./staleness.js";
|
|
15
15
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
16
|
+
export { WRITE_GATED_EXECUTE_STEPS, WRITE_GATED_REQUIRED_STEPS };
|
|
16
17
|
export const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
17
18
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
18
19
|
export const GATED_ENTRYPOINT_COMMANDS = {
|
|
@@ -135,7 +136,37 @@ function shouldRearmDriftProbe(payload, projectRoot, detect) {
|
|
|
135
136
|
return false;
|
|
136
137
|
return detect(projectRoot).inPlay;
|
|
137
138
|
}
|
|
138
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Persist a payload derived from `expected` only while the record on disk is
|
|
141
|
+
* still the one this verification read (#3769).
|
|
142
|
+
*
|
|
143
|
+
* A lease transfer mid-verification can re-arm the tree under a new owner. The
|
|
144
|
+
* losing session is denied by the occupancy recheck, but without this
|
|
145
|
+
* compare-and-swap its in-flight write would already have replaced the new
|
|
146
|
+
* owner record. Serializing ritual writes outright is the separate
|
|
147
|
+
* ritual-state locking item, deliberately out of scope.
|
|
148
|
+
*
|
|
149
|
+
* Returns null on success, or the reason the write was refused.
|
|
150
|
+
*/
|
|
151
|
+
function writeRitualStateIfStillOwned(projectRoot, payload, expected) {
|
|
152
|
+
const [current] = readRitualState(projectRoot);
|
|
153
|
+
if (current === null) {
|
|
154
|
+
return "session ritual state was removed while this verification was running";
|
|
155
|
+
}
|
|
156
|
+
if (current.sessionId !== expected.sessionId ||
|
|
157
|
+
current.startedAt.getTime() !== expected.startedAt.getTime()) {
|
|
158
|
+
return (`session ritual state was re-armed by ${current.sessionId ?? "<unbound>"} while this ` +
|
|
159
|
+
"verification was running; refusing to overwrite the current owner record");
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
writeRitualState(projectRoot, payload);
|
|
163
|
+
}
|
|
164
|
+
catch (exc) {
|
|
165
|
+
return String(exc);
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
function runGatedStep(projectRoot, payload, stepName, runner, now, expected, detect = detectWorkSelection) {
|
|
139
170
|
const command = resolveGatedEntrypointCommand(stepName, projectRoot, detect);
|
|
140
171
|
const { code, stdout, stderr } = runner(command, projectRoot);
|
|
141
172
|
const message = stdout.trim() || stderr.trim() || `${command[0]} exited ${code}`;
|
|
@@ -151,11 +182,9 @@ function runGatedStep(projectRoot, payload, stepName, runner, now, detect = dete
|
|
|
151
182
|
if (stepName === "cache_fresh") {
|
|
152
183
|
applyCacheFreshDriftProbeField(payload, command);
|
|
153
184
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
catch (exc) {
|
|
158
|
-
return `could not write session ritual state after ${stepName}: ${String(exc)}`;
|
|
185
|
+
const refusal = writeRitualStateIfStillOwned(projectRoot, payload, expected);
|
|
186
|
+
if (refusal !== null) {
|
|
187
|
+
return `could not write session ritual state after ${stepName}: ${refusal}`;
|
|
159
188
|
}
|
|
160
189
|
return null;
|
|
161
190
|
}
|
|
@@ -198,14 +227,11 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
198
227
|
return { code: 1, message: headDriftRecoveryMessage(), recoveryTier: "cold" };
|
|
199
228
|
}
|
|
200
229
|
if (input.rebindForwardHead) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
writeRitualState(projectRoot, payload);
|
|
204
|
-
}
|
|
205
|
-
catch (exc) {
|
|
230
|
+
const refusal = writeRitualStateIfStillOwned(projectRoot, { ...state.raw, git_head: currentHead }, { sessionId: state.sessionId, startedAt: state.startedAt });
|
|
231
|
+
if (refusal !== null) {
|
|
206
232
|
return {
|
|
207
233
|
code: 2,
|
|
208
|
-
message: `could not rebind session ritual git HEAD: ${
|
|
234
|
+
message: `could not rebind session ritual git HEAD: ${refusal}`,
|
|
209
235
|
recoveryTier: "cold",
|
|
210
236
|
};
|
|
211
237
|
}
|
|
@@ -217,6 +243,7 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
217
243
|
code: 2,
|
|
218
244
|
message: staleness.error ?? "session ritual staleness policy is invalid",
|
|
219
245
|
recoveryTier: "cold",
|
|
246
|
+
boundSessionId: state.sessionId,
|
|
220
247
|
};
|
|
221
248
|
}
|
|
222
249
|
const maxAgeMs = staleness.hours * 60 * 60 * 1000;
|
|
@@ -231,6 +258,7 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
231
258
|
message: `session ritual state is stale (older than ${staleness.hours}h).${compactNote} ` +
|
|
232
259
|
`Run \`${rearmCmd}\` to re-arm (or \`${coldCmd}\` for a full cold ceremony).`,
|
|
233
260
|
recoveryTier: "rearm",
|
|
261
|
+
boundSessionId: state.sessionId,
|
|
234
262
|
};
|
|
235
263
|
}
|
|
236
264
|
for (const stepName of QUICK_STEPS) {
|
|
@@ -240,17 +268,23 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
240
268
|
code: 1,
|
|
241
269
|
message: failedStepMessage("quick", stepName, step),
|
|
242
270
|
recoveryTier: "cold",
|
|
271
|
+
boundSessionId: state.sessionId,
|
|
243
272
|
};
|
|
244
273
|
}
|
|
245
274
|
}
|
|
246
275
|
if (input.tier === "gated") {
|
|
247
|
-
|
|
276
|
+
const requiredGated = input.requiredGatedSteps ?? GATED_STEPS;
|
|
277
|
+
for (const stepName of requiredGated) {
|
|
248
278
|
const step = state.gatedSteps[stepName];
|
|
249
279
|
if (!stepPasses(step)) {
|
|
280
|
+
const recoveryTier = "cold";
|
|
250
281
|
return {
|
|
251
282
|
code: 1,
|
|
252
|
-
message: failedStepMessage("gated", stepName, step)
|
|
253
|
-
|
|
283
|
+
message: failedStepMessage("gated", stepName, step) +
|
|
284
|
+
" " +
|
|
285
|
+
formatRitualRecoveryInstruction(recoveryTier),
|
|
286
|
+
recoveryTier,
|
|
287
|
+
boundSessionId: state.sessionId,
|
|
254
288
|
};
|
|
255
289
|
}
|
|
256
290
|
}
|
|
@@ -259,14 +293,34 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
259
293
|
code: 0,
|
|
260
294
|
message: `OK session ritual ${input.tier} tier is fresh.`,
|
|
261
295
|
recoveryTier: null,
|
|
296
|
+
boundSessionId: state.sessionId,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Options for the write/spawn mutation verifier (#3738).
|
|
301
|
+
* Required set omits `cache_fresh`; execution authority is `agent_hooks` only.
|
|
302
|
+
*/
|
|
303
|
+
export function writeGateRitualOptions(extras = {}) {
|
|
304
|
+
return {
|
|
305
|
+
...extras,
|
|
306
|
+
tier: "gated",
|
|
307
|
+
posture: extras.posture ?? "mutation",
|
|
308
|
+
requiredGatedSteps: WRITE_GATED_REQUIRED_STEPS,
|
|
309
|
+
executeGatedSteps: WRITE_GATED_EXECUTE_STEPS,
|
|
310
|
+
// Write dispatch already refuses skip (dispatcher passes bypass: false).
|
|
311
|
+
// Default the same here so CI DEFT_SESSION_RITUAL_SKIP=1 cannot empty
|
|
312
|
+
// the execute loop in tests that use this helper (#3738).
|
|
313
|
+
bypass: extras.bypass ?? false,
|
|
314
|
+
envSkip: extras.envSkip ?? "",
|
|
262
315
|
};
|
|
263
316
|
}
|
|
264
317
|
/**
|
|
265
318
|
* Read-only ritual-state inspection for host hooks.
|
|
266
319
|
*
|
|
267
320
|
* Unlike {@link verifySessionRitual}, this never runs missing gated entrypoints
|
|
268
|
-
* and never rewrites `.deft/ritual-state.json`.
|
|
269
|
-
*
|
|
321
|
+
* and never rewrites `.deft/ritual-state.json`. Write dispatch does **not** use
|
|
322
|
+
* this function: it must still execute `agent_hooks` (#3738). A PreToolUse
|
|
323
|
+
* decision must not become a hidden `doctor` / cache-refresh mutation boundary.
|
|
270
324
|
*/
|
|
271
325
|
export function inspectSessionRitual(projectRoot, options = {}) {
|
|
272
326
|
const tier = options.tier ?? "quick";
|
|
@@ -314,6 +368,7 @@ export function inspectSessionRitual(projectRoot, options = {}) {
|
|
|
314
368
|
now: options.now ?? new Date(),
|
|
315
369
|
runGit: options.runGit,
|
|
316
370
|
rebindForwardHead: false,
|
|
371
|
+
requiredGatedSteps: options.requiredGatedSteps,
|
|
317
372
|
});
|
|
318
373
|
return {
|
|
319
374
|
code: evaluated.code,
|
|
@@ -325,6 +380,7 @@ export function inspectSessionRitual(projectRoot, options = {}) {
|
|
|
325
380
|
posture,
|
|
326
381
|
ritualStateRequired,
|
|
327
382
|
recoveryTier: evaluated.recoveryTier,
|
|
383
|
+
...(evaluated.boundSessionId === undefined ? {} : { boundSessionId: evaluated.boundSessionId }),
|
|
328
384
|
};
|
|
329
385
|
}
|
|
330
386
|
export function verifySessionRitual(projectRoot, options = {}) {
|
|
@@ -419,6 +475,9 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
419
475
|
posture,
|
|
420
476
|
ritualStateRequired,
|
|
421
477
|
recoveryTier: precheck.recoveryTier,
|
|
478
|
+
...(precheck.boundSessionId === undefined
|
|
479
|
+
? {}
|
|
480
|
+
: { boundSessionId: precheck.boundSessionId }),
|
|
422
481
|
};
|
|
423
482
|
}
|
|
424
483
|
const reloadedAfterPrecheck = readRitualState(projectRoot);
|
|
@@ -437,12 +496,18 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
437
496
|
};
|
|
438
497
|
}
|
|
439
498
|
const payload = { ...state.raw };
|
|
499
|
+
// Identity of the record this verification is amending; a mid-flight lease
|
|
500
|
+
// transfer must not let these writes land on a newer owner (#3769).
|
|
501
|
+
const ownerAtVerify = { sessionId: state.sessionId, startedAt: state.startedAt };
|
|
440
502
|
const gated = { ...payload.gated_steps };
|
|
441
503
|
payload.gated_steps = gated;
|
|
442
504
|
const runCmd = options.runner ?? defaultRitualRunner;
|
|
443
505
|
const forced = new Set(options.forceGatedSteps ?? []);
|
|
444
506
|
const detect = options.detectWorkSelection ?? detectWorkSelection;
|
|
507
|
+
const executable = new Set(options.executeGatedSteps ?? [...GATED_STEPS]);
|
|
445
508
|
for (const stepName of GATED_STEPS) {
|
|
509
|
+
if (!executable.has(stepName))
|
|
510
|
+
continue;
|
|
446
511
|
const step = gated[stepName];
|
|
447
512
|
if (step?.deferred_reason && stepName !== "agent_hooks")
|
|
448
513
|
continue;
|
|
@@ -453,7 +518,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
453
518
|
if (stepName !== "agent_hooks" && stepPasses(step) && !forced.has(stepName) && !rearmDrift) {
|
|
454
519
|
continue;
|
|
455
520
|
}
|
|
456
|
-
const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant, detect);
|
|
521
|
+
const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant, ownerAtVerify, detect);
|
|
457
522
|
if (writeError !== null) {
|
|
458
523
|
return {
|
|
459
524
|
code: 2,
|
|
@@ -519,6 +584,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
519
584
|
now: instant,
|
|
520
585
|
runGit: options.runGit,
|
|
521
586
|
rebindForwardHead: true,
|
|
587
|
+
requiredGatedSteps: options.requiredGatedSteps,
|
|
522
588
|
});
|
|
523
589
|
if (isBypassed) {
|
|
524
590
|
return {
|
|
@@ -531,6 +597,9 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
531
597
|
posture,
|
|
532
598
|
ritualStateRequired,
|
|
533
599
|
recoveryTier: evaluated.recoveryTier,
|
|
600
|
+
...(evaluated.boundSessionId === undefined
|
|
601
|
+
? {}
|
|
602
|
+
: { boundSessionId: evaluated.boundSessionId }),
|
|
534
603
|
};
|
|
535
604
|
}
|
|
536
605
|
return {
|
|
@@ -543,6 +612,7 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
543
612
|
posture,
|
|
544
613
|
ritualStateRequired,
|
|
545
614
|
recoveryTier: evaluated.recoveryTier,
|
|
615
|
+
...(evaluated.boundSessionId === undefined ? {} : { boundSessionId: evaluated.boundSessionId }),
|
|
546
616
|
};
|
|
547
617
|
}
|
|
548
618
|
export function emitVerifyJson(result) {
|
|
@@ -10,7 +10,7 @@ export declare const REQUIRED_FIELDS: readonly ["slice_id", "umbrella", "umbrell
|
|
|
10
10
|
export declare const OPTIONAL_FIELDS: readonly ["notes"];
|
|
11
11
|
export declare const ALLOWED_FIELDS: Set<"actor" | "slice_id" | "umbrella" | "umbrella_url" | "sliced_at" | "children" | "expected_close_signal" | "notes">;
|
|
12
12
|
export declare const CHILD_REQUIRED_FIELDS: readonly ["n", "url", "wave", "role"];
|
|
13
|
-
export declare const CHILD_ALLOWED_FIELDS: Set<"n" | "
|
|
13
|
+
export declare const CHILD_ALLOWED_FIELDS: Set<"n" | "role" | "url" | "wave">;
|
|
14
14
|
export declare const UUID_RE: RegExp;
|
|
15
15
|
export declare const ISO8601_RE: RegExp;
|
|
16
16
|
export declare const WAVE_FLAG_RE: RegExp;
|
|
@@ -4,10 +4,18 @@ export declare const GENERATED_SPEC_SOURCE_SPEC = "<!-- Source of truth: vbrief/
|
|
|
4
4
|
export declare const GENERATED_SPEC_SOURCE_SPEC_XBRIEF = "<!-- Source of truth: xbrief/specification.xbrief.json -->";
|
|
5
5
|
export declare const GENERATED_SPEC_SOURCE_PD = "<!-- Source of truth: vbrief/PROJECT-DEFINITION.vbrief.json -->";
|
|
6
6
|
export declare const GENERATED_SPEC_SOURCE_PD_XBRIEF = "<!-- Source of truth: xbrief/PROJECT-DEFINITION.xbrief.json -->";
|
|
7
|
-
/**
|
|
8
|
-
export declare function
|
|
7
|
+
/** Return decoded non-PROJECT-DEFINITION source markers from generated markdown. */
|
|
8
|
+
export declare function generatedSpecSourcePaths(content: string): string[];
|
|
9
|
+
/** True when markdown names the expected generated-spec source artifact. */
|
|
10
|
+
export declare function contentHasGeneratedSpecSource(content: string, expectedSourcePath: string): boolean;
|
|
9
11
|
/** True when markdown carries a generated greenfield PD source marker for either layout. */
|
|
10
12
|
export declare function contentHasGeneratedPdSource(content: string): boolean;
|
|
13
|
+
/** Format a source path for a generated artifact banner. */
|
|
14
|
+
export declare function generatedSourcePath(sourcePath: string): string;
|
|
15
|
+
/** Build a canonical four-line generated-artifact banner from the actual source path. */
|
|
16
|
+
export declare function buildGeneratedArtifactBanner(generator: string, purpose: string, sourcePath: string): string;
|
|
17
|
+
/** Build the greenfield project export banner for the resolved PROJECT-DEFINITION path. */
|
|
18
|
+
export declare function buildExportSpecPdBanner(projectDefPath: string): string;
|
|
11
19
|
export declare const EXPORT_SPEC_PD_BANNER: string;
|
|
12
20
|
/** Narratives excluded from stakeholder SPEC/PRD exports (#2013 Wave 0 §5). */
|
|
13
21
|
export declare const STAKEHOLDER_EXCLUDED_NARRATIVE_KEYS: Set<string>;
|
|
@@ -1,22 +1,67 @@
|
|
|
1
|
+
import { basename, dirname, normalize, sep } from "node:path";
|
|
1
2
|
/** Generated-spec banner tokens (lockstep with Python ``_precutover.py``). */
|
|
2
3
|
export const GENERATED_SPEC_PURPOSE = "<!-- Purpose: rendered specification -->";
|
|
3
4
|
export const GENERATED_SPEC_SOURCE_SPEC = "<!-- Source of truth: vbrief/specification.vbrief.json -->";
|
|
4
5
|
export const GENERATED_SPEC_SOURCE_SPEC_XBRIEF = "<!-- Source of truth: xbrief/specification.xbrief.json -->";
|
|
5
6
|
export const GENERATED_SPEC_SOURCE_PD = "<!-- Source of truth: vbrief/PROJECT-DEFINITION.vbrief.json -->";
|
|
6
7
|
export const GENERATED_SPEC_SOURCE_PD_XBRIEF = "<!-- Source of truth: xbrief/PROJECT-DEFINITION.xbrief.json -->";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const GENERATED_SOURCE_MARKER = /^<!-- Source of truth: (.+?) -->\r?$/gm;
|
|
9
|
+
const PROJECT_DEFINITION_SOURCE = /(?:^|\/)PROJECT-DEFINITION\.(?:vbrief|xbrief)\.json$/i;
|
|
10
|
+
/** Return decoded non-PROJECT-DEFINITION source markers from generated markdown. */
|
|
11
|
+
export function generatedSpecSourcePaths(content) {
|
|
12
|
+
const sources = [];
|
|
13
|
+
for (const match of content.matchAll(GENERATED_SOURCE_MARKER)) {
|
|
14
|
+
let source;
|
|
15
|
+
try {
|
|
16
|
+
source = match[1] ? decodeURI(match[1]) : undefined;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// A generated source path is URI-encoded by the banner writer. Reject
|
|
20
|
+
// malformed escapes instead of guessing at a filesystem path.
|
|
21
|
+
}
|
|
22
|
+
if (source && !PROJECT_DEFINITION_SOURCE.test(source))
|
|
23
|
+
sources.push(source);
|
|
24
|
+
}
|
|
25
|
+
return sources;
|
|
26
|
+
}
|
|
27
|
+
/** True when markdown names the expected generated-spec source artifact. */
|
|
28
|
+
export function contentHasGeneratedSpecSource(content, expectedSourcePath) {
|
|
29
|
+
const expected = generatedSourcePath(expectedSourcePath);
|
|
30
|
+
return generatedSpecSourcePaths(content).includes(expected);
|
|
11
31
|
}
|
|
12
32
|
/** True when markdown carries a generated greenfield PD source marker for either layout. */
|
|
13
33
|
export function contentHasGeneratedPdSource(content) {
|
|
14
34
|
return (content.includes(GENERATED_SPEC_SOURCE_PD) || content.includes(GENERATED_SPEC_SOURCE_PD_XBRIEF));
|
|
15
35
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
36
|
+
/** Format a source path for a generated artifact banner. */
|
|
37
|
+
export function generatedSourcePath(sourcePath) {
|
|
38
|
+
const parent = basename(dirname(sourcePath));
|
|
39
|
+
if (parent === "xbrief" || parent === "vbrief") {
|
|
40
|
+
return `${parent}/${basename(sourcePath)}`;
|
|
41
|
+
}
|
|
42
|
+
return normalize(sourcePath).split(sep).join("/");
|
|
43
|
+
}
|
|
44
|
+
function generatedBannerValue(value) {
|
|
45
|
+
return value.replace(/\r\n?|\n/g, " ").replaceAll("-->", "-->");
|
|
46
|
+
}
|
|
47
|
+
/** Build a canonical four-line generated-artifact banner from the actual source path. */
|
|
48
|
+
export function buildGeneratedArtifactBanner(generator, purpose, sourcePath) {
|
|
49
|
+
const safeGenerator = generatedBannerValue(generator);
|
|
50
|
+
const safePurpose = generatedBannerValue(purpose);
|
|
51
|
+
// Source paths must be reversible so an explicit --spec artifact can be
|
|
52
|
+
// bound back to the resolved authority. URI encoding keeps CR/LF and an
|
|
53
|
+
// embedded comment closer on one safe line without losing path identity.
|
|
54
|
+
const safeSourcePath = encodeURI(generatedSourcePath(sourcePath));
|
|
55
|
+
return (`<!-- AUTO-GENERATED by ${safeGenerator} -- DO NOT EDIT MANUALLY -->\n` +
|
|
56
|
+
`<!-- Purpose: ${safePurpose} -->\n` +
|
|
57
|
+
`<!-- Source of truth: ${safeSourcePath} -->\n` +
|
|
58
|
+
`<!-- Regenerate with: ${safeGenerator} -->\n`);
|
|
59
|
+
}
|
|
60
|
+
/** Build the greenfield project export banner for the resolved PROJECT-DEFINITION path. */
|
|
61
|
+
export function buildExportSpecPdBanner(projectDefPath) {
|
|
62
|
+
return buildGeneratedArtifactBanner("task project:export-spec", "rendered specification", projectDefPath);
|
|
63
|
+
}
|
|
64
|
+
export const EXPORT_SPEC_PD_BANNER = buildExportSpecPdBanner("xbrief/PROJECT-DEFINITION.xbrief.json");
|
|
20
65
|
/** Narratives excluded from stakeholder SPEC/PRD exports (#2013 Wave 0 §5). */
|
|
21
66
|
export const STAKEHOLDER_EXCLUDED_NARRATIVE_KEYS = new Set([
|
|
22
67
|
"projectconfig",
|
|
@@ -5,10 +5,13 @@ export interface ResolvedSpecAuthority {
|
|
|
5
5
|
readonly vbriefDir: string;
|
|
6
6
|
readonly projectDefPath: string;
|
|
7
7
|
readonly specPath: string | null;
|
|
8
|
+
readonly sourcePath: string;
|
|
8
9
|
readonly banner: string;
|
|
9
10
|
}
|
|
10
11
|
export declare function resolveSpecAuthority(projectRoot: string): ResolvedSpecAuthority | null;
|
|
11
12
|
export declare function readSpecMarkdown(projectRoot: string): string;
|
|
13
|
+
/** True when the markdown source marker resolves to the project's specification authority. */
|
|
14
|
+
export declare function contentHasResolvedSpecSource(projectRoot: string, content: string): boolean;
|
|
12
15
|
export declare function isFullSpecState(projectRoot: string): boolean;
|
|
13
16
|
export declare function isGreenfieldSpecExport(projectRoot: string): boolean;
|
|
14
17
|
export declare function isCurrentGeneratedSpecification(projectRoot: string): boolean;
|