@deftai/directive-core 0.87.0 → 0.89.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/scanner.d.ts +11 -1
- package/dist/cache/scanner.js +29 -4
- 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/check/gate-lists.js +1 -0
- package/dist/codebase/default-extractor.js +3 -0
- package/dist/codebase/map.js +11 -4
- package/dist/content-contracts/skills/helpers.d.ts +10 -0
- package/dist/content-contracts/skills/helpers.js +35 -0
- package/dist/deposit/copy-tree.d.ts +19 -1
- package/dist/deposit/copy-tree.js +134 -5
- package/dist/doctor/doctor-state.js +28 -4
- package/dist/doctor/main.d.ts +10 -0
- package/dist/doctor/main.js +208 -0
- package/dist/doctor/types.d.ts +11 -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/fs/projection-containment.d.ts +18 -0
- package/dist/fs/projection-containment.js +40 -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 +30 -15
- package/dist/hooks/dispatcher.js +326 -134
- 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/hooks/tools.d.ts +31 -0
- package/dist/hooks/tools.js +74 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +65 -10
- package/dist/init-deposit/gitignore.js +11 -3
- package/dist/init-deposit/headless-manifest.js +12 -4
- package/dist/init-deposit/hygiene.d.ts +16 -0
- package/dist/init-deposit/hygiene.js +26 -0
- package/dist/init-deposit/init-dispatch.js +28 -0
- package/dist/init-deposit/migrate.d.ts +1 -1
- package/dist/init-deposit/migrate.js +13 -3
- package/dist/init-deposit/prettierignore.js +12 -5
- package/dist/init-deposit/refresh.js +38 -7
- package/dist/init-deposit/scaffold.js +42 -29
- package/dist/init-deposit/xbrief-projections.js +17 -10
- 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/intake/issue-ingest.js +11 -2
- package/dist/lifecycle/event-detect.js +12 -4
- package/dist/lifecycle/events.js +29 -20
- package/dist/lifecycle/lifecycle-hygiene.js +9 -3
- package/dist/packs/pack-render.d.ts +33 -0
- package/dist/packs/pack-render.js +172 -13
- package/dist/packs/quarantine-ext.d.ts +10 -0
- package/dist/packs/quarantine-ext.js +26 -2
- 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 +5 -0
- package/dist/policy/index.js +32 -1
- package/dist/policy/intent-ceiling.d.ts +79 -0
- package/dist/policy/intent-ceiling.js +181 -0
- package/dist/policy/no-deft-directive.d.ts +59 -0
- package/dist/policy/no-deft-directive.js +110 -0
- package/dist/policy/org-force-on-migration.d.ts +52 -0
- package/dist/policy/org-force-on-migration.js +269 -27
- 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 +56 -2
- package/dist/policy/runtime-authority.js +297 -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-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/release-availability.js +26 -6
- package/dist/session/ritual-sentinel.js +19 -12
- package/dist/session/session-start-hook.d.ts +3 -0
- package/dist/session/session-start-hook.js +15 -0
- package/dist/session/session-start.js +37 -0
- 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/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/cursor-tier1.js +7 -2
- package/dist/verify-source/index.d.ts +1 -0
- package/dist/verify-source/index.js +1 -0
- package/dist/verify-source/openclaw-tier1.js +19 -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 +16 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync
|
|
1
|
+
import { mkdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
|
+
import { containedWrite } from "../fs/contained-write.js";
|
|
4
5
|
import { resolveTriageCachePath } from "../triage/cache-path.js";
|
|
5
6
|
import { CLEAN_WINDOW_HOURS, DIRTY_WINDOW_HOURS, ENV_STATE_PATH } from "./constants.js";
|
|
6
7
|
const STATE_FILENAME = "doctor-state.json";
|
|
@@ -57,8 +58,31 @@ export function writeState(projectRoot, payload) {
|
|
|
57
58
|
};
|
|
58
59
|
const path = statePath(projectRoot);
|
|
59
60
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
// #2980 wave C: product write sink routes through containedWrite.
|
|
62
|
+
// Default path is under the project triage cache; ENV_STATE_PATH may point outside.
|
|
63
|
+
const projectAbs = resolve(projectRoot);
|
|
64
|
+
const pathAbs = resolve(path);
|
|
65
|
+
const rel = relative(projectAbs, pathAbs);
|
|
66
|
+
const nested = rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
|
|
67
|
+
const payload = `${JSON.stringify(body, null, 2)}\n`;
|
|
68
|
+
if (nested) {
|
|
69
|
+
containedWrite({
|
|
70
|
+
root: projectAbs,
|
|
71
|
+
target: pathAbs,
|
|
72
|
+
data: payload,
|
|
73
|
+
mode: "replace",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
const parent = dirname(pathAbs);
|
|
78
|
+
mkdirSync(parent, { recursive: true });
|
|
79
|
+
containedWrite({
|
|
80
|
+
root: resolve(parent),
|
|
81
|
+
target: basename(pathAbs),
|
|
82
|
+
data: payload,
|
|
83
|
+
mode: "replace",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
62
86
|
return path;
|
|
63
87
|
}
|
|
64
88
|
catch {
|
package/dist/doctor/main.d.ts
CHANGED
|
@@ -4,6 +4,16 @@ import type { DoctorSeams, Finding, ResolutionSummary } from "./types.js";
|
|
|
4
4
|
export declare function cmdDoctor(args: readonly string[], seams?: DoctorSeams): number;
|
|
5
5
|
export declare function runAgentHooksHealthCheck(projectRoot: string, consumerContext: boolean, sink: ReturnType<typeof createPlainSink>, addFinding: (finding: Finding) => void, seams: DoctorSeams): boolean;
|
|
6
6
|
export declare function runAgentHooksLiveProbeCheck(projectRoot: string, sink: ReturnType<typeof createPlainSink>, addFinding: (finding: Finding) => void, seams: DoctorSeams): void;
|
|
7
|
+
/**
|
|
8
|
+
* Fail closed when PROJECT-DEFINITION under an xbrief/ layout still declares
|
|
9
|
+
* envelope 0.6 while the framework schema is 0.8 (#2971). Greenfield (no
|
|
10
|
+
* project definition yet) and current 0.8 envelopes pass. Unreadable paths
|
|
11
|
+
* (test seams / permission) skip rather than false-positive fail. Behind-major
|
|
12
|
+
* records `deft migrate:xbrief` as the next action — layout rename alone is not
|
|
13
|
+
* enough. Distinct from deposited-schema (`stale-xbrief-schema-deposit`) which
|
|
14
|
+
* must NOT route to migrate:xbrief when only framework schema files are stale.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runXbriefEnvelopeVersionCheck(projectRoot: string, sink: ReturnType<typeof createPlainSink>, addFinding: (f: Finding) => void, seams: DoctorSeams): void;
|
|
7
17
|
/**
|
|
8
18
|
* Never emit a bare `task ...` remediation in a project without Taskfile wiring
|
|
9
19
|
* (#2267 / #2893). The `directive`/`deft` surface always works; `task deft:X` is
|
package/dist/doctor/main.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { VBRIEF_VERSION } from "@deftai/directive-types";
|
|
3
4
|
import { evaluate as evaluateAgentsMdAdvisory } from "../agents-md-advisory/evaluate.js";
|
|
4
5
|
import { contentRoot } from "../content-root.js";
|
|
6
|
+
import { resolveProjectDefinitionPath } from "../layout/resolve.js";
|
|
7
|
+
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
|
|
5
8
|
import { describeShadowedPlanExtension, detectShadowedPlanExtensions, } from "../policy/plan-extensions.js";
|
|
6
9
|
import { loadProjectDefinition } from "../policy/resolve.js";
|
|
7
10
|
import { checkLocalEngineIntegrity, classify, detectPackageManager, evaluateSkew, reconcileVersions, plan as resolvePlan, } from "../resolution/index.js";
|
|
11
|
+
import { classifyXbriefSchemaDistance } from "../staleness-tickler/probe-xbrief.js";
|
|
8
12
|
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
9
13
|
import { evaluateAgentHooks } from "../verify-env/agent-hooks.js";
|
|
10
14
|
import { probeAgentHooksLive } from "../verify-env/agent-hooks-live-probe.js";
|
|
15
|
+
import { readDeclaredArtifactVersion } from "../xbrief-migrate/transforms.js";
|
|
11
16
|
import { agentsRefreshPlan, hasV3ManagedMarker } from "./agents-md.js";
|
|
12
17
|
import { runChecks } from "./checks.js";
|
|
13
18
|
import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
|
|
@@ -23,6 +28,8 @@ import { runLocalSignpostChecks } from "./signpost-checks.js";
|
|
|
23
28
|
import { classifyTaskfileInclude, formatGatesSurfaceDualRemediation, formatMissingIncludeSnippet, GATES_SURFACE_DEFT_REMEDIATION, includesBlockHasDeftTaskfile, resolveConsumerTaskfile, } from "./taskfile.js";
|
|
24
29
|
import { defaultWhich } from "./which.js";
|
|
25
30
|
const DEFAULT_RESOLUTION_PLATFORMS = ["linux", "darwin", "win32"];
|
|
31
|
+
/** Next-action command for project-envelope behind-major (#2971). */
|
|
32
|
+
const XBRIEF_ENVELOPE_MIGRATE_COMMAND = "deft migrate:xbrief";
|
|
26
33
|
/**
|
|
27
34
|
* Read the `packageManager` field (Corepack) from a project package.json, or
|
|
28
35
|
* null when absent/unreadable. Lets the doctor detect a pnpm project that has
|
|
@@ -62,6 +69,53 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
62
69
|
const consumerContext = resolve(projectRoot) !== resolve(frameworkRoot);
|
|
63
70
|
const whichFn = seams.whichFn ?? defaultWhich;
|
|
64
71
|
const nowFn = seams.now ?? (() => new Date());
|
|
72
|
+
// #2926: official root opt-out — short-circuit Directive doctor when clean;
|
|
73
|
+
// diagnose flag+deposit inconsistency (warn; exit dirty).
|
|
74
|
+
const optOut = detectNoDeftDirective(projectRoot);
|
|
75
|
+
if (optOut.present) {
|
|
76
|
+
if (optOut.inconsistent) {
|
|
77
|
+
const message = `${NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE} (${NO_DEFT_DIRECTIVE_DISABLED_MESSAGE})`;
|
|
78
|
+
if (jsonMode) {
|
|
79
|
+
const payload = {
|
|
80
|
+
status: "disabled-inconsistent",
|
|
81
|
+
disabled: true,
|
|
82
|
+
disabled_via: NO_DEFT_DIRECTIVE_FLAG_NAME,
|
|
83
|
+
inconsistent: true,
|
|
84
|
+
inconsistent_policy: NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY,
|
|
85
|
+
deposit_present: true,
|
|
86
|
+
message,
|
|
87
|
+
findings: [
|
|
88
|
+
{
|
|
89
|
+
severity: "warning",
|
|
90
|
+
message,
|
|
91
|
+
check: "no-deft-directive",
|
|
92
|
+
policy: NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
process.stdout.write(`${pythonJsonDump(payload)}\n`);
|
|
97
|
+
}
|
|
98
|
+
else if (!quietMode) {
|
|
99
|
+
process.stderr.write(`${message} [policy=${NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY}]\n`);
|
|
100
|
+
}
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
if (jsonMode) {
|
|
104
|
+
const payload = {
|
|
105
|
+
status: "disabled",
|
|
106
|
+
disabled: true,
|
|
107
|
+
disabled_via: NO_DEFT_DIRECTIVE_FLAG_NAME,
|
|
108
|
+
inconsistent: false,
|
|
109
|
+
deposit_present: false,
|
|
110
|
+
message: NO_DEFT_DIRECTIVE_DISABLED_MESSAGE,
|
|
111
|
+
};
|
|
112
|
+
process.stdout.write(`${pythonJsonDump(payload)}\n`);
|
|
113
|
+
}
|
|
114
|
+
else if (!quietMode) {
|
|
115
|
+
process.stdout.write(`${NO_DEFT_DIRECTIVE_DISABLED_MESSAGE}\n`);
|
|
116
|
+
}
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
65
119
|
if (!fullMode) {
|
|
66
120
|
const state = (seams.readState ?? readState)(projectRoot);
|
|
67
121
|
const decision = decideThrottle(state, nowFn());
|
|
@@ -293,6 +347,11 @@ export function cmdDoctor(args, seams = {}) {
|
|
|
293
347
|
}
|
|
294
348
|
sink.info("Checking gates-surface readiness (Taskfile include for deep-think agent gates)...");
|
|
295
349
|
runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFinding, seams);
|
|
350
|
+
if (!jsonMode) {
|
|
351
|
+
sink.blank();
|
|
352
|
+
}
|
|
353
|
+
sink.info("Checking xBRIEF project envelope version...");
|
|
354
|
+
runXbriefEnvelopeVersionCheck(projectRoot, sink, addFinding, seams);
|
|
296
355
|
let resolution = null;
|
|
297
356
|
if (!runningInsideDeftRepo(projectRoot, seams)) {
|
|
298
357
|
if (!jsonMode) {
|
|
@@ -722,6 +781,155 @@ function runTaskfileIncludeCheck(projectRoot, fixMode, jsonMode, sink, addFindin
|
|
|
722
781
|
suggestion: GATES_SURFACE_DEFT_REMEDIATION,
|
|
723
782
|
});
|
|
724
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Fail closed when PROJECT-DEFINITION under an xbrief/ layout still declares
|
|
786
|
+
* envelope 0.6 while the framework schema is 0.8 (#2971). Greenfield (no
|
|
787
|
+
* project definition yet) and current 0.8 envelopes pass. Unreadable paths
|
|
788
|
+
* (test seams / permission) skip rather than false-positive fail. Behind-major
|
|
789
|
+
* records `deft migrate:xbrief` as the next action — layout rename alone is not
|
|
790
|
+
* enough. Distinct from deposited-schema (`stale-xbrief-schema-deposit`) which
|
|
791
|
+
* must NOT route to migrate:xbrief when only framework schema files are stale.
|
|
792
|
+
*/
|
|
793
|
+
export function runXbriefEnvelopeVersionCheck(projectRoot, sink, addFinding, seams) {
|
|
794
|
+
const checkName = "xbrief-envelope-version";
|
|
795
|
+
const isFile = seams.isFile ?? ((p) => existsSync(p));
|
|
796
|
+
const readText = seams.readText ?? readTextSafe;
|
|
797
|
+
const targetVersion = VBRIEF_VERSION;
|
|
798
|
+
if (seams.probeXbriefEnvelope) {
|
|
799
|
+
const probe = seams.probeXbriefEnvelope(projectRoot);
|
|
800
|
+
emitXbriefEnvelopeFinding(checkName, probe, sink, addFinding);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
let definitionPath;
|
|
804
|
+
try {
|
|
805
|
+
definitionPath = resolveProjectDefinitionPath(projectRoot);
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
// Pure vbrief/-only trees are already covered by layout migrate signposts.
|
|
809
|
+
const skipMessage = `${checkName}: skip -- legacy-only layout (use layout migrate first)`;
|
|
810
|
+
sink.info(skipMessage);
|
|
811
|
+
addFinding({
|
|
812
|
+
severity: "skip",
|
|
813
|
+
message: skipMessage,
|
|
814
|
+
check: checkName,
|
|
815
|
+
status: "skip",
|
|
816
|
+
reason: "legacy-only-layout",
|
|
817
|
+
});
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
if (!isFile(definitionPath)) {
|
|
821
|
+
const skipMessage = `${checkName}: skip -- no PROJECT-DEFINITION yet (greenfield)`;
|
|
822
|
+
sink.info(skipMessage);
|
|
823
|
+
addFinding({
|
|
824
|
+
severity: "skip",
|
|
825
|
+
message: skipMessage,
|
|
826
|
+
check: checkName,
|
|
827
|
+
status: "skip",
|
|
828
|
+
reason: "no-project-definition",
|
|
829
|
+
});
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
const text = readText(definitionPath);
|
|
833
|
+
if (text === null) {
|
|
834
|
+
// Unreadable path (permissions or injectable seams) is not proof of 0.6.
|
|
835
|
+
const skipMessage = `${checkName}: skip -- PROJECT-DEFINITION unreadable`;
|
|
836
|
+
sink.info(skipMessage);
|
|
837
|
+
addFinding({
|
|
838
|
+
severity: "skip",
|
|
839
|
+
message: skipMessage,
|
|
840
|
+
check: checkName,
|
|
841
|
+
status: "skip",
|
|
842
|
+
reason: "unreadable",
|
|
843
|
+
});
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
let declaredVersion = null;
|
|
847
|
+
try {
|
|
848
|
+
const parsed = JSON.parse(text);
|
|
849
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
850
|
+
declaredVersion = readDeclaredArtifactVersion(parsed);
|
|
851
|
+
}
|
|
852
|
+
else {
|
|
853
|
+
const skipMessage = `${checkName}: skip -- PROJECT-DEFINITION is not a JSON object`;
|
|
854
|
+
sink.info(skipMessage);
|
|
855
|
+
addFinding({
|
|
856
|
+
severity: "skip",
|
|
857
|
+
message: skipMessage,
|
|
858
|
+
check: checkName,
|
|
859
|
+
status: "skip",
|
|
860
|
+
reason: "invalid-json-shape",
|
|
861
|
+
});
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
catch {
|
|
866
|
+
const skipMessage = `${checkName}: skip -- PROJECT-DEFINITION JSON parse failed`;
|
|
867
|
+
sink.info(skipMessage);
|
|
868
|
+
addFinding({
|
|
869
|
+
severity: "skip",
|
|
870
|
+
message: skipMessage,
|
|
871
|
+
check: checkName,
|
|
872
|
+
status: "skip",
|
|
873
|
+
reason: "parse-error",
|
|
874
|
+
});
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
const distance = classifyXbriefSchemaDistance(declaredVersion, targetVersion);
|
|
878
|
+
emitXbriefEnvelopeFinding(checkName, {
|
|
879
|
+
declaredVersion,
|
|
880
|
+
targetVersion,
|
|
881
|
+
distance,
|
|
882
|
+
stale: distance !== "current",
|
|
883
|
+
}, sink, addFinding);
|
|
884
|
+
}
|
|
885
|
+
function emitXbriefEnvelopeFinding(checkName, probe, sink, addFinding) {
|
|
886
|
+
if (probe.distance === "current") {
|
|
887
|
+
const okMessage = `${checkName}: current -- xBRIEFInfo@${probe.declaredVersion ?? probe.targetVersion} ` +
|
|
888
|
+
`(framework ${probe.targetVersion})`;
|
|
889
|
+
sink.success(okMessage);
|
|
890
|
+
addFinding({
|
|
891
|
+
severity: "skip",
|
|
892
|
+
message: okMessage,
|
|
893
|
+
check: checkName,
|
|
894
|
+
status: "current",
|
|
895
|
+
declared_version: probe.declaredVersion,
|
|
896
|
+
target_version: probe.targetVersion,
|
|
897
|
+
});
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
if (probe.distance === "behind-minor") {
|
|
901
|
+
const warnMessage = `${checkName}: behind-minor -- declared ${probe.declaredVersion ?? "unknown"}, ` +
|
|
902
|
+
`framework ${probe.targetVersion}. Prefer \`deft migrate:xbrief\` when convenient.`;
|
|
903
|
+
sink.warn(warnMessage);
|
|
904
|
+
addFinding({
|
|
905
|
+
severity: "warning",
|
|
906
|
+
message: warnMessage,
|
|
907
|
+
check: checkName,
|
|
908
|
+
status: "behind-minor",
|
|
909
|
+
suggestion: XBRIEF_ENVELOPE_MIGRATE_COMMAND,
|
|
910
|
+
declared_version: probe.declaredVersion,
|
|
911
|
+
target_version: probe.targetVersion,
|
|
912
|
+
next_command: XBRIEF_ENVELOPE_MIGRATE_COMMAND,
|
|
913
|
+
});
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
// behind-major (declared 0.6 vs framework 0.8, dual half-state, missing version)
|
|
917
|
+
const nextCommand = XBRIEF_ENVELOPE_MIGRATE_COMMAND;
|
|
918
|
+
const message = `${checkName}: behind-major -- declared ${probe.declaredVersion ?? "unknown"}, ` +
|
|
919
|
+
`framework ${probe.targetVersion}. Next action: run \`${nextCommand}\` to bump project ` +
|
|
920
|
+
`JSON envelopes to xBRIEFInfo@${probe.targetVersion} (layout rename alone is not enough).`;
|
|
921
|
+
sink.error(message);
|
|
922
|
+
addFinding({
|
|
923
|
+
severity: "error",
|
|
924
|
+
message,
|
|
925
|
+
check: checkName,
|
|
926
|
+
status: "behind-major",
|
|
927
|
+
suggestion: nextCommand,
|
|
928
|
+
declared_version: probe.declaredVersion,
|
|
929
|
+
target_version: probe.targetVersion,
|
|
930
|
+
next_command: nextCommand,
|
|
931
|
+
});
|
|
932
|
+
}
|
|
725
933
|
/**
|
|
726
934
|
* Surface the resolved USER.md path + which search rung matched (#2271) so the
|
|
727
935
|
* resolution boundary is visible in doctor output. Uses the shared first-hit-
|
package/dist/doctor/types.d.ts
CHANGED
|
@@ -150,5 +150,16 @@ export interface DoctorSeams {
|
|
|
150
150
|
readonly evaluateAgentHooks?: (projectRoot: string) => AgentHookHealthResult;
|
|
151
151
|
/** Live hook spawn probe for doctor --full (#2852). */
|
|
152
152
|
readonly probeAgentHooksLive?: (projectRoot: string) => AgentHookLiveProbeResult;
|
|
153
|
+
/**
|
|
154
|
+
* xBRIEF project-envelope staleness probe (#2971). Injected so doctor can
|
|
155
|
+
* fail closed on 0.6 project JSON under an xbrief/ layout without re-deriving
|
|
156
|
+
* the shared `probeXbriefStaleness` path in tests.
|
|
157
|
+
*/
|
|
158
|
+
readonly probeXbriefEnvelope?: (projectRoot: string) => {
|
|
159
|
+
readonly declaredVersion: string | null;
|
|
160
|
+
readonly targetVersion: string;
|
|
161
|
+
readonly distance: "current" | "behind-minor" | "behind-major";
|
|
162
|
+
readonly stale: boolean;
|
|
163
|
+
};
|
|
153
164
|
}
|
|
154
165
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-facing escalation actions: file, list, resolve, batch-approve (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { type EscalationEvent, type EscalationType } from "./types.js";
|
|
5
|
+
export interface FileEscalationInput {
|
|
6
|
+
readonly projectRoot: string;
|
|
7
|
+
readonly type: string;
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly body?: string;
|
|
10
|
+
readonly agentId?: string;
|
|
11
|
+
readonly contextRefs?: readonly string[];
|
|
12
|
+
readonly slaHours?: number;
|
|
13
|
+
readonly dangerous?: boolean;
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly now?: Date;
|
|
16
|
+
}
|
|
17
|
+
export declare function fileEscalation(input: FileEscalationInput): EscalationEvent;
|
|
18
|
+
export interface ResolveEscalationInput {
|
|
19
|
+
readonly projectRoot: string;
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly decision: string;
|
|
22
|
+
readonly actor?: string;
|
|
23
|
+
readonly note?: string | null;
|
|
24
|
+
readonly answer?: string | null;
|
|
25
|
+
readonly now?: Date;
|
|
26
|
+
}
|
|
27
|
+
export type ResolveEscalationResult = {
|
|
28
|
+
readonly ok: true;
|
|
29
|
+
readonly event: EscalationEvent;
|
|
30
|
+
} | {
|
|
31
|
+
readonly ok: false;
|
|
32
|
+
readonly code: "not-found" | "already-resolved" | "invalid-decision";
|
|
33
|
+
readonly message: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function resolveEscalation(input: ResolveEscalationInput): ResolveEscalationResult;
|
|
36
|
+
export interface BatchApproveInput {
|
|
37
|
+
readonly projectRoot: string;
|
|
38
|
+
/** Limit to these ids; empty/undefined = all open batch-eligible. */
|
|
39
|
+
readonly ids?: readonly string[];
|
|
40
|
+
readonly actor?: string;
|
|
41
|
+
readonly note?: string | null;
|
|
42
|
+
/** When false (default), skip `dangerous: true` items. */
|
|
43
|
+
readonly includeDangerous?: boolean;
|
|
44
|
+
readonly now?: Date;
|
|
45
|
+
}
|
|
46
|
+
export interface BatchApproveResult {
|
|
47
|
+
readonly approved: EscalationEvent[];
|
|
48
|
+
readonly skipped: Array<{
|
|
49
|
+
id: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Bulk-approve only `cmd_approval` and `question` open items.
|
|
55
|
+
* design_decision / approval / resource / external must use individual resolve.
|
|
56
|
+
* Dangerous items are skipped unless `includeDangerous` is true.
|
|
57
|
+
*/
|
|
58
|
+
export declare function batchApproveEscalations(input: BatchApproveInput): BatchApproveResult;
|
|
59
|
+
export declare function listEscalationsFiltered(projectRoot: string, opts?: {
|
|
60
|
+
openOnly?: boolean;
|
|
61
|
+
type?: EscalationType;
|
|
62
|
+
}): EscalationEvent[];
|
|
63
|
+
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operator-facing escalation actions: file, list, resolve, batch-approve (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { listEscalations, listOpenEscalations, loadEscalation, parseEscalation, saveEscalation, utcIso, validateEscalationType, } from "./store.js";
|
|
6
|
+
import { BATCH_APPROVABLE_TYPES, DEFAULT_SLA_HOURS, ESCALATION_SCHEMA_VERSION, isBatchApprovableType, isEscalationDecision, } from "./types.js";
|
|
7
|
+
function newEscalationId(now) {
|
|
8
|
+
const ts = (now ?? new Date())
|
|
9
|
+
.toISOString()
|
|
10
|
+
.replace(/[-:TZ.]/g, "")
|
|
11
|
+
.slice(0, 14);
|
|
12
|
+
const suffix = randomBytes(3).toString("hex");
|
|
13
|
+
return `esc-${ts}-${suffix}`;
|
|
14
|
+
}
|
|
15
|
+
export function fileEscalation(input) {
|
|
16
|
+
const type = validateEscalationType(input.type);
|
|
17
|
+
const title = input.title.trim();
|
|
18
|
+
if (title.length === 0) {
|
|
19
|
+
throw new Error("escalation title must be non-empty");
|
|
20
|
+
}
|
|
21
|
+
const createdAt = utcIso(input.now);
|
|
22
|
+
const event = {
|
|
23
|
+
schemaVersion: ESCALATION_SCHEMA_VERSION,
|
|
24
|
+
id: input.id?.trim() || newEscalationId(input.now),
|
|
25
|
+
agentId: (input.agentId ?? "agent").trim() || "agent",
|
|
26
|
+
type,
|
|
27
|
+
title,
|
|
28
|
+
body: input.body ?? "",
|
|
29
|
+
contextRefs: input.contextRefs ?? [],
|
|
30
|
+
createdAt,
|
|
31
|
+
slaHours: input.slaHours !== undefined && input.slaHours > 0 ? input.slaHours : DEFAULT_SLA_HOURS[type],
|
|
32
|
+
status: "open",
|
|
33
|
+
dangerous: input.dangerous === true,
|
|
34
|
+
resolution: null,
|
|
35
|
+
};
|
|
36
|
+
// Round-trip through parse to guarantee store shape.
|
|
37
|
+
const parsed = parseEscalation(event);
|
|
38
|
+
if (parsed === null) {
|
|
39
|
+
throw new Error("internal: filed escalation failed validation");
|
|
40
|
+
}
|
|
41
|
+
saveEscalation(input.projectRoot, parsed);
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
export function resolveEscalation(input) {
|
|
45
|
+
const decisionRaw = input.decision.trim().toLowerCase();
|
|
46
|
+
if (!isEscalationDecision(decisionRaw)) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
code: "invalid-decision",
|
|
50
|
+
message: `invalid decision '${input.decision}'; expected one of: approved, denied, answered, dismissed`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const existing = loadEscalation(input.projectRoot, input.id);
|
|
54
|
+
if (existing === null) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
code: "not-found",
|
|
58
|
+
message: `escalation not found: ${input.id}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (existing.status !== "open") {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
code: "already-resolved",
|
|
65
|
+
message: `escalation already resolved: ${input.id}`,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const resolved = {
|
|
69
|
+
...existing,
|
|
70
|
+
status: "resolved",
|
|
71
|
+
resolution: {
|
|
72
|
+
decision: decisionRaw,
|
|
73
|
+
resolvedAt: utcIso(input.now),
|
|
74
|
+
resolvedBy: (input.actor ?? "operator").trim() || "operator",
|
|
75
|
+
note: input.note ?? null,
|
|
76
|
+
answer: input.answer ?? null,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
saveEscalation(input.projectRoot, resolved);
|
|
80
|
+
return { ok: true, event: resolved };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Bulk-approve only `cmd_approval` and `question` open items.
|
|
84
|
+
* design_decision / approval / resource / external must use individual resolve.
|
|
85
|
+
* Dangerous items are skipped unless `includeDangerous` is true.
|
|
86
|
+
*/
|
|
87
|
+
export function batchApproveEscalations(input) {
|
|
88
|
+
const open = listOpenEscalations(input.projectRoot);
|
|
89
|
+
const idFilter = input.ids !== undefined && input.ids.length > 0
|
|
90
|
+
? new Set(input.ids.map((x) => x.trim()).filter((x) => x.length > 0))
|
|
91
|
+
: null;
|
|
92
|
+
const approved = [];
|
|
93
|
+
const skipped = [];
|
|
94
|
+
for (const event of open) {
|
|
95
|
+
if (idFilter !== null && !idFilter.has(event.id)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (!isBatchApprovableType(event.type)) {
|
|
99
|
+
skipped.push({
|
|
100
|
+
id: event.id,
|
|
101
|
+
reason: `type ${event.type} is not batch-approvable (use escalation:resolve); allowed bulk types: ${BATCH_APPROVABLE_TYPES.join(", ")}`,
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (event.dangerous && input.includeDangerous !== true) {
|
|
106
|
+
skipped.push({
|
|
107
|
+
id: event.id,
|
|
108
|
+
reason: "dangerous=true — resolve individually or pass --include-dangerous",
|
|
109
|
+
});
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const decision = event.type === "question" ? "answered" : "approved";
|
|
113
|
+
const result = resolveEscalation({
|
|
114
|
+
projectRoot: input.projectRoot,
|
|
115
|
+
id: event.id,
|
|
116
|
+
decision,
|
|
117
|
+
actor: input.actor,
|
|
118
|
+
note: input.note ?? "batch-approve",
|
|
119
|
+
answer: event.type === "question" ? (input.note ?? "acknowledged") : null,
|
|
120
|
+
now: input.now,
|
|
121
|
+
});
|
|
122
|
+
if (result.ok) {
|
|
123
|
+
approved.push(result.event);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
skipped.push({ id: event.id, reason: result.message });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { approved, skipped };
|
|
130
|
+
}
|
|
131
|
+
export function listEscalationsFiltered(projectRoot, opts = {}) {
|
|
132
|
+
const base = opts.openOnly === true ? listOpenEscalations(projectRoot) : listEscalations(projectRoot);
|
|
133
|
+
if (opts.type === undefined)
|
|
134
|
+
return base;
|
|
135
|
+
return base.filter((e) => e.type === opts.type);
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=actions.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { ESCALATION_DIR } from "./types.js";
|
|
3
|
+
export function escalationsDir(projectRoot) {
|
|
4
|
+
return join(projectRoot, ...ESCALATION_DIR.split("/"));
|
|
5
|
+
}
|
|
6
|
+
export function escalationPath(projectRoot, escalationId) {
|
|
7
|
+
const safe = escalationId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
8
|
+
return join(escalationsDir(projectRoot), `${safe}.json`);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk store for typed escalations under `.deft/escalations/` (#518).
|
|
3
|
+
*/
|
|
4
|
+
import { type EscalationEvent, type EscalationType } from "./types.js";
|
|
5
|
+
export declare function utcIso(now?: Date): string;
|
|
6
|
+
/**
|
|
7
|
+
* Parse an escalation JSON object.
|
|
8
|
+
* Returns null when type/status/required fields are invalid (tests reject invalid types).
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseEscalation(raw: unknown): EscalationEvent | null;
|
|
11
|
+
/** Validate type string alone (unit tests + CLI). */
|
|
12
|
+
export declare function validateEscalationType(type: string): EscalationType;
|
|
13
|
+
export declare function saveEscalation(projectRoot: string, event: EscalationEvent): void;
|
|
14
|
+
export declare function loadEscalation(projectRoot: string, escalationId: string): EscalationEvent | null;
|
|
15
|
+
export declare function listEscalations(projectRoot: string): EscalationEvent[];
|
|
16
|
+
export declare function listOpenEscalations(projectRoot: string): EscalationEvent[];
|
|
17
|
+
//# sourceMappingURL=store.d.ts.map
|