@deftai/directive-core 0.88.0 → 0.90.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 +55 -0
- package/dist/authz/actions.js +125 -0
- package/dist/authz/classify.d.ts +23 -0
- package/dist/authz/classify.js +217 -0
- package/dist/authz/closed-verb.d.ts +42 -0
- package/dist/authz/closed-verb.js +279 -0
- package/dist/authz/evaluate.d.ts +41 -0
- package/dist/authz/evaluate.js +298 -0
- package/dist/authz/index.d.ts +15 -0
- package/dist/authz/index.js +15 -0
- package/dist/authz/origin.d.ts +28 -0
- package/dist/authz/origin.js +64 -0
- package/dist/authz/paths.d.ts +6 -0
- package/dist/authz/paths.js +19 -0
- package/dist/authz/store.d.ts +40 -0
- package/dist/authz/store.js +317 -0
- package/dist/authz/templates.d.ts +139 -0
- package/dist/authz/templates.js +241 -0
- package/dist/authz/types.d.ts +111 -0
- package/dist/authz/types.js +41 -0
- package/dist/authz/verb-classification.d.ts +44 -0
- package/dist/authz/verb-classification.js +237 -0
- package/dist/branch/evaluate.js +6 -1
- package/dist/cache/fetch.js +10 -5
- package/dist/cache/io.d.ts +9 -2
- package/dist/cache/io.js +30 -10
- package/dist/cache/task-cache/store.js +11 -7
- package/dist/capacity/backfill.js +10 -4
- package/dist/category-b-namespace/index.js +10 -4
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/doctor/constants.d.ts +1 -1
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/flags.js +21 -1
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +182 -0
- package/dist/doctor/openclaw-skills.d.ts +109 -0
- package/dist/doctor/openclaw-skills.js +463 -0
- package/dist/doctor/types.d.ts +22 -0
- package/dist/escalation/actions.d.ts +63 -0
- package/dist/escalation/actions.js +137 -0
- package/dist/escalation/index.d.ts +8 -0
- package/dist/escalation/index.js +8 -0
- package/dist/escalation/paths.d.ts +3 -0
- package/dist/escalation/paths.js +10 -0
- package/dist/escalation/store.d.ts +17 -0
- package/dist/escalation/store.js +172 -0
- package/dist/escalation/types.d.ts +73 -0
- package/dist/escalation/types.js +43 -0
- package/dist/eval/crud-telemetry.js +30 -10
- package/dist/eval/health.js +22 -7
- package/dist/eval/readback.js +11 -10
- package/dist/eval/run.js +32 -16
- package/dist/events/attribution-enrichment.js +10 -4
- package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
- package/dist/finish-loop/directive-finish-loop.js +239 -0
- package/dist/finish-loop/grant-gate.d.ts +31 -0
- package/dist/finish-loop/grant-gate.js +169 -0
- package/dist/finish-loop/index.d.ts +11 -0
- package/dist/finish-loop/index.js +11 -0
- package/dist/finish-loop/main.d.ts +35 -0
- package/dist/finish-loop/main.js +365 -0
- package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
- package/dist/finish-loop/pr-finish-loop.js +217 -0
- package/dist/finish-loop/progress.d.ts +17 -0
- package/dist/finish-loop/progress.js +45 -0
- package/dist/finish-loop/queue.d.ts +16 -0
- package/dist/finish-loop/queue.js +65 -0
- package/dist/finish-loop/types.d.ts +52 -0
- package/dist/finish-loop/types.js +10 -0
- package/dist/fs/contained-write.d.ts +101 -0
- package/dist/fs/contained-write.js +281 -0
- package/dist/hooks/classify/classify.d.ts +10 -0
- package/dist/hooks/classify/classify.js +37 -0
- package/dist/hooks/classify/index.d.ts +14 -0
- package/dist/hooks/classify/index.js +13 -0
- package/dist/hooks/classify/paths.d.ts +22 -0
- package/dist/hooks/classify/paths.js +72 -0
- package/dist/hooks/classify/payload.d.ts +16 -0
- package/dist/hooks/classify/payload.js +45 -0
- package/dist/hooks/classify/stdin.d.ts +12 -0
- package/dist/hooks/classify/stdin.js +63 -0
- package/dist/hooks/classify/tool-name.d.ts +18 -0
- package/dist/hooks/classify/tool-name.js +85 -0
- package/dist/hooks/classify/types.d.ts +41 -0
- package/dist/hooks/classify/types.js +7 -0
- package/dist/hooks/dispatcher.d.ts +21 -18
- package/dist/hooks/dispatcher.js +232 -168
- package/dist/hooks/fixtures/cases.d.ts +44 -0
- package/dist/hooks/fixtures/cases.js +595 -0
- package/dist/hooks/fixtures/index.d.ts +2 -0
- package/dist/hooks/fixtures/index.js +2 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +6 -0
- package/dist/init-deposit/agent-hooks.js +27 -8
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +10 -3
- package/dist/init-deposit/scaffold.js +35 -26
- package/dist/init-deposit/xbrief-projections.js +11 -4
- package/dist/intake/candidates-log.js +46 -35
- package/dist/intake/github-body-cli.d.ts +6 -0
- package/dist/intake/github-body-cli.js +17 -0
- package/dist/intake/github-body.d.ts +46 -3
- package/dist/intake/github-body.js +191 -20
- package/dist/intake/issue-emit.d.ts +1 -0
- package/dist/intake/issue-emit.js +41 -23
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +31 -20
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/lifecycle/stats.d.ts +53 -0
- package/dist/lifecycle/stats.js +286 -0
- package/dist/packs/pack-render.js +19 -6
- package/dist/plan-sequence/store.js +11 -4
- package/dist/policy/hotfix-criteria.d.ts +79 -0
- package/dist/policy/hotfix-criteria.js +197 -0
- package/dist/policy/index.d.ts +4 -0
- package/dist/policy/index.js +31 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.js +10 -3
- package/dist/policy/org-force-on-migration.js +9 -5
- package/dist/policy/require-human-merge.d.ts +75 -0
- package/dist/policy/require-human-merge.js +324 -0
- package/dist/policy/resolve.js +17 -6
- package/dist/policy/runtime-authority.d.ts +15 -2
- package/dist/policy/runtime-authority.js +23 -3
- package/dist/policy/write-fence.d.ts +78 -0
- package/dist/policy/write-fence.js +164 -0
- package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
- package/dist/pr-wait-mergeable/cascade.js +28 -0
- package/dist/preflight/evaluate.js +10 -0
- package/dist/product-signal/consent.js +21 -5
- package/dist/product-signal/submit.js +22 -12
- package/dist/release/build-dist-runner.js +5 -2
- package/dist/release/build-dist.d.ts +19 -1
- package/dist/release/build-dist.js +50 -2
- package/dist/release/native-steps.js +7 -2
- package/dist/release/spawn.js +13 -0
- package/dist/release-e2e/git-ops.d.ts +7 -0
- package/dist/release-e2e/git-ops.js +35 -2
- package/dist/release-publish/pipeline.d.ts +13 -0
- package/dist/release-publish/pipeline.js +46 -1
- package/dist/scope/audit-log.js +19 -24
- package/dist/scope/decompose.js +10 -5
- package/dist/scope/decomposed-refs.js +18 -3
- package/dist/scope/demote.js +9 -2
- package/dist/scope/undo.js +9 -2
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/process-cost-constants.d.ts +9 -0
- package/dist/session/process-cost-constants.js +11 -0
- package/dist/session/process-cost.d.ts +53 -0
- package/dist/session/process-cost.js +82 -0
- package/dist/session/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +31 -13
- package/dist/session/session-ready.d.ts +67 -0
- package/dist/session/session-ready.js +264 -0
- package/dist/session/session-start.d.ts +56 -1
- package/dist/session/session-start.js +384 -23
- package/dist/session/verify-session-ritual.d.ts +8 -0
- package/dist/session/verify-session-ritual.js +95 -35
- package/dist/staleness-tickler/state.js +26 -6
- package/dist/swarm/launch.js +13 -3
- package/dist/swarm/routing.js +9 -3
- package/dist/task-surface/index.js +24 -9
- package/dist/tool-events/classify.d.ts +20 -0
- package/dist/tool-events/classify.js +634 -0
- package/dist/tool-events/index.d.ts +10 -0
- package/dist/tool-events/index.js +10 -0
- package/dist/tool-events/summarize.d.ts +34 -0
- package/dist/tool-events/summarize.js +93 -0
- package/dist/tool-events/types.d.ts +52 -0
- package/dist/tool-events/types.js +13 -0
- package/dist/triage/bootstrap/gitignore.js +53 -10
- package/dist/triage/cache-path.js +10 -3
- package/dist/triage/welcome/summary.js +11 -4
- package/dist/triage/welcome/writers.js +45 -16
- package/dist/validate-content/validate-links.js +5 -0
- package/dist/value/readback.js +11 -6
- package/dist/vbrief-activate/activate.js +12 -4
- package/dist/vbrief-build/project-definition-io.js +14 -6
- package/dist/verify-source/contained-writes.d.ts +59 -0
- package/dist/verify-source/contained-writes.js +272 -0
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +14 -1
- package/dist/verify-source/verify-stubs.js +3 -0
- package/dist/xbrief-migrate/migrate-project.js +26 -12
- package/dist/xbrief-migrate/transforms.d.ts +4 -2
- package/dist/xbrief-migrate/transforms.js +37 -14
- package/package.json +19 -3
|
@@ -1,10 +1,34 @@
|
|
|
1
|
+
import { closedVerbEnvBypassKey, evaluateClosedVerb } from "../authz/closed-verb.js";
|
|
2
|
+
import { listActiveHumanGrants, loadAuthzState, markGrantUsed } from "../authz/store.js";
|
|
3
|
+
import { loadVerbClassification } from "../authz/verb-classification.js";
|
|
1
4
|
import { EXIT_OK, EXIT_VIOLATION } from "../release/constants.js";
|
|
2
5
|
import { editReleasePublish, viewRelease } from "./gh-api.js";
|
|
3
6
|
export function emit(label, status) {
|
|
4
7
|
process.stderr.write(`[publish] ${label}... ${status}\n`);
|
|
5
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Fail-closed gate before draft→public (#1095 Wave 4).
|
|
11
|
+
* Allow only DEFT_ALLOW_RELEASE_PUBLISH=1 or a matching human-origin grant.
|
|
12
|
+
* Loads conventions/verb-classification.json from projectRoot when present.
|
|
13
|
+
* Test seam: pass grants / env via optional overrides on seams when present.
|
|
14
|
+
*/
|
|
15
|
+
export function evaluateReleasePublishGate(version, projectRoot, options = {}) {
|
|
16
|
+
// Production load of classification SoT (conventions/verb-classification.json).
|
|
17
|
+
const classification = loadVerbClassification(projectRoot);
|
|
18
|
+
const state = loadAuthzState(projectRoot);
|
|
19
|
+
const grants = options.grants ?? listActiveHumanGrants(projectRoot, state);
|
|
20
|
+
return evaluateClosedVerb({
|
|
21
|
+
verb: "release-publish",
|
|
22
|
+
target: version,
|
|
23
|
+
grants,
|
|
24
|
+
env: options.env ?? process.env,
|
|
25
|
+
projectRoot,
|
|
26
|
+
repo: options.repo ?? null,
|
|
27
|
+
classification,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
6
30
|
export function runPublish(config, seams = {}) {
|
|
7
|
-
const { version, repo, dryRun } = config;
|
|
31
|
+
const { version, repo, dryRun, projectRoot } = config;
|
|
8
32
|
const tag = `v${version}`;
|
|
9
33
|
const viewLabel = `View ${tag} on ${repo}`;
|
|
10
34
|
if (dryRun) {
|
|
@@ -26,6 +50,23 @@ export function runPublish(config, seams = {}) {
|
|
|
26
50
|
return EXIT_OK;
|
|
27
51
|
}
|
|
28
52
|
emit(viewLabel, `OK (draft found at ${payload?.url ?? "<no url>"})`);
|
|
53
|
+
// Closed-verb gate: refuse draft→public without grant or env bypass (#1095).
|
|
54
|
+
const gateSeams = seams;
|
|
55
|
+
const gate = evaluateReleasePublishGate(version, projectRoot, {
|
|
56
|
+
grants: gateSeams.closedVerbGrants,
|
|
57
|
+
env: gateSeams.closedVerbEnv,
|
|
58
|
+
repo,
|
|
59
|
+
});
|
|
60
|
+
if (!gate.allowed) {
|
|
61
|
+
const bypassKey = gate.envBypassKey ??
|
|
62
|
+
closedVerbEnvBypassKey("release-publish", projectRoot) ??
|
|
63
|
+
"DEFT_ALLOW_RELEASE_PUBLISH";
|
|
64
|
+
emit(`Closed-verb gate release-publish ${tag}`, `FAIL (${gate.code}: ${gate.reason})`);
|
|
65
|
+
process.stderr.write(`[publish] denied code=${gate.code} — set ${bypassKey}=1 or ` +
|
|
66
|
+
`mint \`deft authz:grant -- --template release-publish --target ${version}\`\n`);
|
|
67
|
+
return EXIT_VIOLATION;
|
|
68
|
+
}
|
|
69
|
+
emit(`Closed-verb gate release-publish ${tag}`, `OK (${gate.code}${gate.humanApprovalRef !== null ? ` grant=${gate.humanApprovalRef}` : ""})`);
|
|
29
70
|
const editLabel = `Edit ${tag} (--draft=false)`;
|
|
30
71
|
const [ok, editReason] = editReleasePublish(version, repo, payload?.id ?? undefined, seams);
|
|
31
72
|
if (!ok) {
|
|
@@ -40,6 +81,10 @@ export function runPublish(config, seams = {}) {
|
|
|
40
81
|
return EXIT_VIOLATION;
|
|
41
82
|
}
|
|
42
83
|
emit(verifyLabel, `OK (${tag} is now public)`);
|
|
84
|
+
// Consume single-use grant after successful draft→public (#1095).
|
|
85
|
+
if (gate.humanApprovalRef !== null && gate.code === "closed-verb-allow") {
|
|
86
|
+
markGrantUsed(projectRoot, gate.humanApprovalRef);
|
|
87
|
+
}
|
|
43
88
|
process.stderr.write(`Release ${tag} published successfully on ${repo}.\n`);
|
|
44
89
|
return EXIT_OK;
|
|
45
90
|
}
|
package/dist/scope/audit-log.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { dirname,
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
5
6
|
import { utcNowIso } from "./vbrief-json.js";
|
|
6
7
|
export class ScopeAuditLogError extends Error {
|
|
@@ -28,6 +29,12 @@ const DEMOTE_META_REQUIRED = [
|
|
|
28
29
|
"demoted_from",
|
|
29
30
|
];
|
|
30
31
|
let threadLock = false;
|
|
32
|
+
/** Ensure parent exists and return it as the containment root for log writes. */
|
|
33
|
+
function containmentRootForLog(logPath) {
|
|
34
|
+
const parent = dirname(logPath);
|
|
35
|
+
mkdirSync(parent, { recursive: true });
|
|
36
|
+
return resolve(parent);
|
|
37
|
+
}
|
|
31
38
|
function replacerSortKeys(_key, value) {
|
|
32
39
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
33
40
|
const sorted = {};
|
|
@@ -97,34 +104,16 @@ export function canonicalLogPath(projectRoot) {
|
|
|
97
104
|
// Layout-aware (#1703): triage working-set under `.triage-cache/`.
|
|
98
105
|
return resolveTriageCachePath(resolve(projectRoot), "scope-lifecycle.jsonl");
|
|
99
106
|
}
|
|
100
|
-
function withAppendLock(
|
|
101
|
-
const lockPath = `${logPath}.lock`;
|
|
102
|
-
mkdirSync(dirname(logPath), { recursive: true });
|
|
107
|
+
function withAppendLock(fn) {
|
|
103
108
|
while (threadLock) {
|
|
104
109
|
/* spin-wait for in-process serialization */
|
|
105
110
|
}
|
|
106
111
|
threadLock = true;
|
|
107
|
-
let fd = null;
|
|
108
112
|
try {
|
|
109
|
-
fd = openSync(lockPath, "a+");
|
|
110
|
-
if (readFileSync(lockPath).length === 0) {
|
|
111
|
-
writeSync(fd, "\0");
|
|
112
|
-
}
|
|
113
113
|
fn();
|
|
114
114
|
}
|
|
115
115
|
finally {
|
|
116
116
|
threadLock = false;
|
|
117
|
-
if (fd !== null) {
|
|
118
|
-
closeSync(fd);
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
|
-
if (existsSync(lockPath)) {
|
|
122
|
-
unlinkSync(lockPath);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
/* best-effort lock cleanup */
|
|
127
|
-
}
|
|
128
117
|
}
|
|
129
118
|
}
|
|
130
119
|
/** Validate and append an audit entry; returns decision_id. */
|
|
@@ -134,10 +123,16 @@ export function append(entry, logPath) {
|
|
|
134
123
|
}
|
|
135
124
|
validateEntry(entry);
|
|
136
125
|
const logFile = resolve(logPath);
|
|
137
|
-
|
|
126
|
+
const root = containmentRootForLog(logFile);
|
|
138
127
|
const line = JSON.stringify(entry, replacerSortKeys);
|
|
139
|
-
|
|
140
|
-
|
|
128
|
+
// #2980 wave B: product write sink routes through containedWrite.
|
|
129
|
+
withAppendLock(() => {
|
|
130
|
+
containedWrite({
|
|
131
|
+
root,
|
|
132
|
+
target: logFile,
|
|
133
|
+
data: `${line}\n`,
|
|
134
|
+
mode: "append",
|
|
135
|
+
});
|
|
141
136
|
});
|
|
142
137
|
return String(entry.decision_id);
|
|
143
138
|
}
|
package/dist/scope/decompose.js
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
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
|
|
9
|
+
import { accessSync, constants, existsSync, mkdirSync, readFileSync } 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 {
|
|
12
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
13
|
+
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
13
14
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
14
15
|
import { referenceWithDefaultTrust, slugify } from "../vbrief-build/build.js";
|
|
15
16
|
import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
|
|
@@ -54,9 +55,13 @@ function loadJson(path) {
|
|
|
54
55
|
return data;
|
|
55
56
|
}
|
|
56
57
|
function writeJson(projectRoot, path, data) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
59
|
+
containedWrite({
|
|
60
|
+
root: resolve(projectRoot),
|
|
61
|
+
target: path,
|
|
62
|
+
data: formatBriefJson(data),
|
|
63
|
+
mode: "replace",
|
|
64
|
+
});
|
|
60
65
|
}
|
|
61
66
|
// ---------------------------------------------------------------------------
|
|
62
67
|
// Story quality helpers (ported from _vbrief_story_quality.py)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, relative, resolve } from "node:path";
|
|
3
3
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { formatBriefJson } from "./vbrief-json.js";
|
|
5
6
|
import { collectChildUris, collectPlanRefs, resolveVbriefRef } from "./vbrief-ref.js";
|
|
6
7
|
function rewriteOnePlanRef(value, oldParentResolved, newParentRel, vbriefDir) {
|
|
@@ -53,7 +54,14 @@ function rewriteParentChildReference(parentPath, oldChildResolved, newChildRel,
|
|
|
53
54
|
}
|
|
54
55
|
if (changed) {
|
|
55
56
|
try {
|
|
56
|
-
|
|
57
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
58
|
+
const dir = dirname(parentPath);
|
|
59
|
+
containedWrite({
|
|
60
|
+
root: resolve(dir),
|
|
61
|
+
target: parentPath,
|
|
62
|
+
data: formatBriefJson(parentData),
|
|
63
|
+
mode: "replace",
|
|
64
|
+
});
|
|
57
65
|
}
|
|
58
66
|
catch {
|
|
59
67
|
return false;
|
|
@@ -127,7 +135,14 @@ function rewriteChildParentReference(childPath, oldParentResolved, newParentRel,
|
|
|
127
135
|
}
|
|
128
136
|
if (changed) {
|
|
129
137
|
try {
|
|
130
|
-
|
|
138
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
139
|
+
const dir = dirname(childPath);
|
|
140
|
+
containedWrite({
|
|
141
|
+
root: resolve(dir),
|
|
142
|
+
target: childPath,
|
|
143
|
+
data: formatBriefJson(childData),
|
|
144
|
+
mode: "replace",
|
|
145
|
+
});
|
|
131
146
|
}
|
|
132
147
|
catch {
|
|
133
148
|
return false;
|
package/dist/scope/demote.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
4
5
|
import { hasArtifactSuffix, resolveLifecycleFolder } from "../layout/resolve.js";
|
|
5
6
|
import { stripTrailingPathSeparators } from "../text/redos-safe.js";
|
|
@@ -85,7 +86,13 @@ export function demoteOne(filePath, projectRoot, reason, options = {}) {
|
|
|
85
86
|
const timestamp = utcNowIso(now);
|
|
86
87
|
planObj.status = TARGET_STATUS;
|
|
87
88
|
planObj.updated = timestamp;
|
|
88
|
-
|
|
89
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
90
|
+
containedWrite({
|
|
91
|
+
root: resolve(projectRoot),
|
|
92
|
+
target: resolved,
|
|
93
|
+
data: formatBriefJson(data),
|
|
94
|
+
mode: "replace",
|
|
95
|
+
});
|
|
89
96
|
const vbriefRoot = dirname(dirname(resolved));
|
|
90
97
|
const targetDir = join(vbriefRoot, TARGET_FOLDER);
|
|
91
98
|
mkdirSync(targetDir, { recursive: true });
|
package/dist/scope/undo.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
|
|
2
2
|
import { basename, join, relative, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
4
5
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
5
6
|
import { append, canonicalLogPath, newDecisionId, readAll } from "./audit-log.js";
|
|
@@ -185,7 +186,13 @@ function moveAndFlip(srcFile, destFolder, newStatus, timestamp, projectRoot) {
|
|
|
185
186
|
const planObj = plan;
|
|
186
187
|
planObj.status = newStatus;
|
|
187
188
|
planObj.updated = timestamp;
|
|
188
|
-
|
|
189
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
190
|
+
containedWrite({
|
|
191
|
+
root: resolve(projectRoot),
|
|
192
|
+
target: srcFile,
|
|
193
|
+
data: formatBriefJson(data),
|
|
194
|
+
mode: "replace",
|
|
195
|
+
});
|
|
189
196
|
mkdirSync(destFolder, { recursive: true });
|
|
190
197
|
const destPath = join(destFolder, basename(srcFile));
|
|
191
198
|
renameSync(srcFile, destPath);
|
package/dist/session/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from "./git.js";
|
|
2
2
|
export * from "./json.js";
|
|
3
3
|
export * from "./posture.js";
|
|
4
|
+
export * from "./process-cost.js";
|
|
4
5
|
export * from "./resume-conditions.js";
|
|
5
6
|
export * from "./ritual-sentinel.js";
|
|
7
|
+
export * from "./session-ready.js";
|
|
6
8
|
export * from "./session-start.js";
|
|
7
9
|
export * from "./session-start-hook.js";
|
|
8
10
|
export * from "./time.js";
|
package/dist/session/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from "./git.js";
|
|
2
2
|
export * from "./json.js";
|
|
3
3
|
export * from "./posture.js";
|
|
4
|
+
export * from "./process-cost.js";
|
|
4
5
|
export * from "./resume-conditions.js";
|
|
5
6
|
export * from "./ritual-sentinel.js";
|
|
7
|
+
export * from "./session-ready.js";
|
|
6
8
|
export * from "./session-start.js";
|
|
7
9
|
export * from "./session-start-hook.js";
|
|
8
10
|
export * from "./time.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Canonical process-cost event names for ceremony observability (#2994). */
|
|
2
|
+
export declare const PROCESS_COST_EVENT_NAMES: {
|
|
3
|
+
readonly sessionStart: "session:start";
|
|
4
|
+
readonly sessionRitualBlocked: "session:ritual-blocked";
|
|
5
|
+
};
|
|
6
|
+
export type ProcessCostEventName = (typeof PROCESS_COST_EVENT_NAMES)[keyof typeof PROCESS_COST_EVENT_NAMES];
|
|
7
|
+
/** Required payload keys per process-cost event (merged into lifecycle/events). */
|
|
8
|
+
export declare const PROCESS_COST_REQUIRED_PAYLOAD: Readonly<Record<string, readonly string[]>>;
|
|
9
|
+
//# sourceMappingURL=process-cost-constants.d.ts.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Canonical process-cost event names for ceremony observability (#2994). */
|
|
2
|
+
export const PROCESS_COST_EVENT_NAMES = {
|
|
3
|
+
sessionStart: "session:start",
|
|
4
|
+
sessionRitualBlocked: "session:ritual-blocked",
|
|
5
|
+
};
|
|
6
|
+
/** Required payload keys per process-cost event (merged into lifecycle/events). */
|
|
7
|
+
export const PROCESS_COST_REQUIRED_PAYLOAD = {
|
|
8
|
+
[PROCESS_COST_EVENT_NAMES.sessionStart]: ["ceremony_tier", "duration_ms", "exit_code"],
|
|
9
|
+
[PROCESS_COST_EVENT_NAMES.sessionRitualBlocked]: ["tool_name", "code"],
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=process-cost-constants.js.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local process-cost ceremony events (#2994).
|
|
3
|
+
*
|
|
4
|
+
* Always-on, best-effort appends to `.deft-cache/events.jsonl`.
|
|
5
|
+
* Not gated on valueFeedback (distinct from #1709 attribution).
|
|
6
|
+
* No remote / Product Insights upload (#2603 not required).
|
|
7
|
+
*
|
|
8
|
+
* Types are declared locally (not imported from session-start) so this module
|
|
9
|
+
* does not form an import cycle with session-start call sites.
|
|
10
|
+
*/
|
|
11
|
+
import { type BehavioralEventRecord } from "../lifecycle/events.js";
|
|
12
|
+
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, type ProcessCostEventName, } from "./process-cost-constants.js";
|
|
13
|
+
/** Ceremony tier labels mirror session-start cold|rearm (#2992 / #2994). */
|
|
14
|
+
export type ProcessCostCeremonyTier = "cold" | "rearm";
|
|
15
|
+
export interface ProcessCostStepTiming {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
readonly duration_ms: number;
|
|
18
|
+
readonly skipped?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface EmitProcessCostOptions {
|
|
21
|
+
readonly projectRoot: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional explicit log path for tests.
|
|
24
|
+
* When omitted, `emit` resolves via DEFT_EVENT_LOG then `.deft-cache/events.jsonl`
|
|
25
|
+
* (must not pre-resolve the default here or DEFT_EVENT_LOG is bypassed).
|
|
26
|
+
*/
|
|
27
|
+
readonly logPath?: string | null;
|
|
28
|
+
}
|
|
29
|
+
export interface SessionStartProcessCostInput {
|
|
30
|
+
readonly ceremonyTier: ProcessCostCeremonyTier;
|
|
31
|
+
readonly durationMs: number;
|
|
32
|
+
readonly exitCode: number;
|
|
33
|
+
readonly ready?: boolean;
|
|
34
|
+
readonly optionalNetwork?: boolean;
|
|
35
|
+
readonly steps?: readonly ProcessCostStepTiming[];
|
|
36
|
+
}
|
|
37
|
+
export interface SessionRitualBlockedProcessCostInput {
|
|
38
|
+
readonly toolName: string;
|
|
39
|
+
readonly code?: string;
|
|
40
|
+
readonly recoveryTier?: "cold" | "rearm";
|
|
41
|
+
readonly detail?: string;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Emit `session:start` after cold/re-arm ceremony completes (or fails early).
|
|
45
|
+
* Returns null on any failure (telemetry must not interrupt session:start).
|
|
46
|
+
*/
|
|
47
|
+
export declare function emitSessionStartProcessCost(input: SessionStartProcessCostInput, options: EmitProcessCostOptions): BehavioralEventRecord | null;
|
|
48
|
+
/**
|
|
49
|
+
* Emit `session:ritual-blocked` on PreToolUse ritual-not-ready deny.
|
|
50
|
+
* Returns null on any failure (telemetry must not change deny verdict).
|
|
51
|
+
*/
|
|
52
|
+
export declare function emitSessionRitualBlockedProcessCost(input: SessionRitualBlockedProcessCostInput, options: EmitProcessCostOptions): BehavioralEventRecord | null;
|
|
53
|
+
//# sourceMappingURL=process-cost.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local process-cost ceremony events (#2994).
|
|
3
|
+
*
|
|
4
|
+
* Always-on, best-effort appends to `.deft-cache/events.jsonl`.
|
|
5
|
+
* Not gated on valueFeedback (distinct from #1709 attribution).
|
|
6
|
+
* No remote / Product Insights upload (#2603 not required).
|
|
7
|
+
*
|
|
8
|
+
* Types are declared locally (not imported from session-start) so this module
|
|
9
|
+
* does not form an import cycle with session-start call sites.
|
|
10
|
+
*/
|
|
11
|
+
import { emit } from "../lifecycle/events.js";
|
|
12
|
+
import { PROCESS_COST_EVENT_NAMES } from "./process-cost-constants.js";
|
|
13
|
+
export { PROCESS_COST_EVENT_NAMES, PROCESS_COST_REQUIRED_PAYLOAD, } from "./process-cost-constants.js";
|
|
14
|
+
/**
|
|
15
|
+
* Emit `session:start` after cold/re-arm ceremony completes (or fails early).
|
|
16
|
+
* Returns null on any failure (telemetry must not interrupt session:start).
|
|
17
|
+
*/
|
|
18
|
+
export function emitSessionStartProcessCost(input, options) {
|
|
19
|
+
try {
|
|
20
|
+
const payload = {
|
|
21
|
+
ceremony_tier: input.ceremonyTier,
|
|
22
|
+
duration_ms: input.durationMs,
|
|
23
|
+
exit_code: input.exitCode,
|
|
24
|
+
};
|
|
25
|
+
if (input.ready !== undefined) {
|
|
26
|
+
payload.ready = input.ready;
|
|
27
|
+
}
|
|
28
|
+
if (input.optionalNetwork !== undefined) {
|
|
29
|
+
payload.optional_network = input.optionalNetwork;
|
|
30
|
+
}
|
|
31
|
+
if (input.steps !== undefined) {
|
|
32
|
+
payload.steps = input.steps.map((step) => {
|
|
33
|
+
const entry = {
|
|
34
|
+
name: step.name,
|
|
35
|
+
duration_ms: step.duration_ms,
|
|
36
|
+
};
|
|
37
|
+
if (step.skipped === true) {
|
|
38
|
+
entry.skipped = true;
|
|
39
|
+
}
|
|
40
|
+
return entry;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// Pass logPath only when caller overrides; otherwise emit honors DEFT_EVENT_LOG.
|
|
44
|
+
const emitOptions = { projectRoot: options.projectRoot };
|
|
45
|
+
if (options.logPath !== undefined) {
|
|
46
|
+
emitOptions.logPath = options.logPath;
|
|
47
|
+
}
|
|
48
|
+
return emit(PROCESS_COST_EVENT_NAMES.sessionStart, payload, emitOptions);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Emit `session:ritual-blocked` on PreToolUse ritual-not-ready deny.
|
|
56
|
+
* Returns null on any failure (telemetry must not change deny verdict).
|
|
57
|
+
*/
|
|
58
|
+
export function emitSessionRitualBlockedProcessCost(input, options) {
|
|
59
|
+
try {
|
|
60
|
+
const payload = {
|
|
61
|
+
tool_name: input.toolName,
|
|
62
|
+
code: input.code ?? "ritual-not-ready",
|
|
63
|
+
};
|
|
64
|
+
if (input.recoveryTier !== undefined) {
|
|
65
|
+
payload.recovery_tier = input.recoveryTier;
|
|
66
|
+
}
|
|
67
|
+
if (input.detail !== undefined && input.detail.length > 0) {
|
|
68
|
+
// Cap free-form detail so JSONL lines stay bounded.
|
|
69
|
+
payload.detail =
|
|
70
|
+
input.detail.length > 240 ? `${input.detail.slice(0, 237)}...` : input.detail;
|
|
71
|
+
}
|
|
72
|
+
const emitOptions = { projectRoot: options.projectRoot };
|
|
73
|
+
if (options.logPath !== undefined) {
|
|
74
|
+
emitOptions.logPath = options.logPath;
|
|
75
|
+
}
|
|
76
|
+
return emit(PROCESS_COST_EVENT_NAMES.sessionRitualBlocked, payload, emitOptions);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=process-cost.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, renameSync,
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { locateManifest, parseInstallManifest } from "../doctor/manifest.js";
|
|
5
5
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
6
6
|
import { evaluateReleaseAvailability } from "../doctor/release-availability.js";
|
|
7
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
7
8
|
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
8
9
|
const THROTTLE_MS = 24 * 60 * 60 * 1000;
|
|
9
10
|
const PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -56,10 +57,29 @@ function isThrottled(state, latestVersion, now) {
|
|
|
56
57
|
return Number.isFinite(notifiedAt) && now.getTime() - notifiedAt < THROTTLE_MS;
|
|
57
58
|
}
|
|
58
59
|
function defaultWriteState(path, content) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
const dir = dirname(path);
|
|
61
|
+
mkdirSync(dir, { recursive: true });
|
|
62
|
+
const tmpBase = `${basename(path)}.${process.pid}.tmp`;
|
|
63
|
+
const temporary = join(dir, tmpBase);
|
|
64
|
+
try {
|
|
65
|
+
// #2980 wave D: product write sink routes through containedWrite.
|
|
66
|
+
containedWrite({
|
|
67
|
+
root: resolve(dir),
|
|
68
|
+
target: tmpBase,
|
|
69
|
+
data: content,
|
|
70
|
+
mode: "create",
|
|
71
|
+
});
|
|
72
|
+
renameSync(temporary, path);
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
try {
|
|
76
|
+
rmSync(temporary, { force: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* best-effort cleanup */
|
|
80
|
+
}
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
63
83
|
}
|
|
64
84
|
/**
|
|
65
85
|
* Probe the public npm registry separately from doctor. Doctor stays offline
|
|
@@ -30,6 +30,8 @@ export interface RitualStepInput {
|
|
|
30
30
|
readonly exitCode?: number | null;
|
|
31
31
|
readonly message?: string | null;
|
|
32
32
|
readonly command?: readonly string[] | null;
|
|
33
|
+
/** Wall-clock ms for this step when measured (#2991). */
|
|
34
|
+
readonly durationMs?: number | null;
|
|
33
35
|
}
|
|
34
36
|
export declare function ritualStatePath(projectRoot: string): string;
|
|
35
37
|
export declare function ritualStep(input: RitualStepInput): Record<string, unknown>;
|
|
@@ -53,10 +55,13 @@ export interface MarkRitualStaleAfterCompactResult {
|
|
|
53
55
|
/**
|
|
54
56
|
* Invalidate an existing mutation ritual after host context compaction/resume (#2113).
|
|
55
57
|
* Reuses ritual-state age semantics — no parallel policy stack.
|
|
58
|
+
* Marks rearm_needed so recovery messaging prefers session:start --rearm when cold is unnecessary (#2992).
|
|
56
59
|
*/
|
|
57
60
|
export declare function markRitualStaleAfterCompact(projectRoot: string, input?: {
|
|
58
61
|
now?: Date;
|
|
59
62
|
}): MarkRitualStaleAfterCompactResult;
|
|
63
|
+
/** Whether ritual-state.json marks compact/age recovery as re-arm preferred (#2992). */
|
|
64
|
+
export declare function ritualStateMarksRearmNeeded(state: RitualState): boolean;
|
|
60
65
|
export declare function recordRitualStep(projectRoot: string, input: {
|
|
61
66
|
tier: "quick" | "gated";
|
|
62
67
|
stepName: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, } from "node:fs";
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
3
4
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
|
|
4
5
|
import { stableJson } from "./json.js";
|
|
5
6
|
import { RITUAL_STATE_CONTRACT } from "./posture.js";
|
|
@@ -34,6 +35,9 @@ export function ritualStep(input) {
|
|
|
34
35
|
if (input.command && input.command.length > 0) {
|
|
35
36
|
payload.command = input.command.map(String);
|
|
36
37
|
}
|
|
38
|
+
if (input.durationMs !== null && input.durationMs !== undefined) {
|
|
39
|
+
payload.duration_ms = Math.max(0, Math.round(input.durationMs));
|
|
40
|
+
}
|
|
37
41
|
return payload;
|
|
38
42
|
}
|
|
39
43
|
export function newRitualStatePayload(input) {
|
|
@@ -92,24 +96,30 @@ function validateSteps(raw, key) {
|
|
|
92
96
|
return [steps, null];
|
|
93
97
|
}
|
|
94
98
|
function atomicWriteJson(targetPath, payload, prefix) {
|
|
95
|
-
mkdirSync(join(targetPath, ".."), { recursive: true });
|
|
96
99
|
const dir = join(targetPath, "..");
|
|
97
|
-
|
|
98
|
-
const
|
|
100
|
+
mkdirSync(dir, { recursive: true });
|
|
101
|
+
const tmpBase = `${prefix}${process.pid}.json.tmp`;
|
|
102
|
+
const tmpName = join(dir, tmpBase);
|
|
103
|
+
const text = `${stableJson(payload, 2)}\n`;
|
|
99
104
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
// #2980 wave D: ritual-state product write routes through containedWrite.
|
|
106
|
+
containedWrite({
|
|
107
|
+
root: resolve(dir),
|
|
108
|
+
target: tmpBase,
|
|
109
|
+
data: text,
|
|
110
|
+
mode: "create",
|
|
111
|
+
});
|
|
112
|
+
renameSync(tmpName, targetPath);
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
102
115
|
try {
|
|
103
|
-
|
|
116
|
+
rmSync(tmpName, { force: true });
|
|
104
117
|
}
|
|
105
118
|
catch {
|
|
106
|
-
|
|
119
|
+
/* best-effort cleanup */
|
|
107
120
|
}
|
|
121
|
+
throw err;
|
|
108
122
|
}
|
|
109
|
-
finally {
|
|
110
|
-
closeSync(fd);
|
|
111
|
-
}
|
|
112
|
-
renameSync(tmpName, targetPath);
|
|
113
123
|
}
|
|
114
124
|
export function readRitualState(projectRoot) {
|
|
115
125
|
const stateFile = ritualStatePath(projectRoot);
|
|
@@ -189,6 +199,7 @@ export const RITUAL_STALE_EPOCH = new Date("1970-01-01T00:00:00Z");
|
|
|
189
199
|
/**
|
|
190
200
|
* Invalidate an existing mutation ritual after host context compaction/resume (#2113).
|
|
191
201
|
* Reuses ritual-state age semantics — no parallel policy stack.
|
|
202
|
+
* Marks rearm_needed so recovery messaging prefers session:start --rearm when cold is unnecessary (#2992).
|
|
192
203
|
*/
|
|
193
204
|
export function markRitualStaleAfterCompact(projectRoot, input = {}) {
|
|
194
205
|
const now = input.now ?? new Date();
|
|
@@ -204,14 +215,21 @@ export function markRitualStaleAfterCompact(projectRoot, input = {}) {
|
|
|
204
215
|
const payload = { ...state.raw };
|
|
205
216
|
payload.started_at = timestampIso(RITUAL_STALE_EPOCH);
|
|
206
217
|
payload.compact_resume_at = timestampIso(now);
|
|
218
|
+
// #2992: compact invalidates the ritual clock only — prefer re-arm recovery when worktree/HEAD allow.
|
|
219
|
+
payload.rearm_needed = true;
|
|
207
220
|
writeRitualState(projectRoot, payload);
|
|
208
221
|
return {
|
|
209
222
|
changed: true,
|
|
210
223
|
statePath,
|
|
211
|
-
message: "Marked session ritual
|
|
224
|
+
message: "Marked session ritual re-arm needed after context compaction; run " +
|
|
225
|
+
"session:start --rearm (or full session:start if worktree/HEAD changed) and " +
|
|
212
226
|
"verify:session-ritual -- --tier=gated before direct writes.",
|
|
213
227
|
};
|
|
214
228
|
}
|
|
229
|
+
/** Whether ritual-state.json marks compact/age recovery as re-arm preferred (#2992). */
|
|
230
|
+
export function ritualStateMarksRearmNeeded(state) {
|
|
231
|
+
return state.raw.rearm_needed === true || typeof state.raw.compact_resume_at === "string";
|
|
232
|
+
}
|
|
215
233
|
export function recordRitualStep(projectRoot, input) {
|
|
216
234
|
const [state, err] = readRitualState(projectRoot);
|
|
217
235
|
if (state === null) {
|