@deftai/directive-core 0.104.0 → 0.105.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/classify.js +526 -42
- package/dist/check/cached-orchestrator.d.ts +1 -1
- package/dist/check/cached-orchestrator.js +2 -2
- package/dist/check/named-cause.d.ts +1 -1
- package/dist/check/named-cause.js +33 -3
- package/dist/codebase/default-extractor.d.ts +5 -0
- package/dist/codebase/default-extractor.js +7 -15
- package/dist/deposit/copy-tree.js +60 -0
- package/dist/deposit/python-free.js +26 -18
- package/dist/doctor/checks.d.ts +8 -0
- package/dist/doctor/checks.js +37 -25
- package/dist/doctor/doctor-state.d.ts +4 -0
- package/dist/doctor/doctor-state.js +7 -1
- package/dist/doctor/main.js +9 -19
- package/dist/doctor/types.d.ts +9 -0
- package/dist/fs/contained-write.d.ts +115 -1
- package/dist/fs/contained-write.js +241 -5
- package/dist/fs/mutation-ledger.d.ts +54 -0
- package/dist/fs/mutation-ledger.js +133 -0
- package/dist/fs/non-product-dirs.d.ts +79 -0
- package/dist/fs/non-product-dirs.js +96 -0
- package/dist/hooks/dispatcher.d.ts +3 -1
- package/dist/hooks/dispatcher.js +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.js +17 -15
- package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
- package/dist/init-deposit/core-guard-pin-content.js +393 -0
- package/dist/init-deposit/gitignore.js +9 -0
- package/dist/init-deposit/hygiene.d.ts +47 -11
- package/dist/init-deposit/hygiene.js +172 -37
- package/dist/init-deposit/legacy-detect.js +6 -2
- package/dist/init-deposit/refresh.d.ts +21 -1
- package/dist/init-deposit/refresh.js +106 -9
- package/dist/init-deposit/scaffold.js +28 -279
- package/dist/init-deposit/slash-deposit.js +13 -7
- package/dist/init-deposit/xbrief-projections.d.ts +8 -1
- package/dist/init-deposit/xbrief-projections.js +24 -11
- package/dist/intake/clause-derivation.d.ts +38 -1
- package/dist/intake/clause-derivation.js +211 -9
- package/dist/intake/issue-ingest.d.ts +3 -1
- package/dist/intake/issue-ingest.js +35 -6
- package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
- package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
- package/dist/literal-acceptance/capture.d.ts +10 -0
- package/dist/literal-acceptance/capture.js +224 -75
- package/dist/literal-acceptance/evaluate.d.ts +29 -4
- package/dist/literal-acceptance/evaluate.js +70 -2
- package/dist/literal-acceptance/index.d.ts +3 -3
- package/dist/literal-acceptance/index.js +3 -3
- package/dist/literal-acceptance/safety.d.ts +41 -0
- package/dist/literal-acceptance/safety.js +126 -2
- package/dist/literal-acceptance/types.d.ts +8 -0
- package/dist/orphan-active/evaluate.d.ts +7 -1
- package/dist/orphan-active/evaluate.js +102 -23
- package/dist/platform/platform-capabilities.js +2 -3
- package/dist/policy/base-branch.d.ts +37 -0
- package/dist/policy/base-branch.js +114 -0
- package/dist/policy/branch-sync.d.ts +80 -0
- package/dist/policy/branch-sync.js +211 -0
- package/dist/policy/ceremony-dial-escalation.js +1 -0
- package/dist/policy/ceremony-dial.js +4 -14
- package/dist/policy/delivery-branch.d.ts +5 -3
- package/dist/policy/delivery-branch.js +7 -3
- package/dist/policy/file-size-thresholds.d.ts +24 -0
- package/dist/policy/file-size-thresholds.js +23 -0
- package/dist/policy/forge-outage-retry.d.ts +47 -0
- package/dist/policy/forge-outage-retry.js +157 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.js +66 -7
- package/dist/policy/project-invariants.d.ts +50 -0
- package/dist/policy/project-invariants.js +205 -0
- package/dist/policy/sync-default.d.ts +129 -0
- package/dist/policy/sync-default.js +452 -0
- package/dist/policy/sync-max-files.d.ts +93 -0
- package/dist/policy/sync-max-files.js +192 -0
- package/dist/pr-monitor/absent-required.d.ts +11 -0
- package/dist/pr-monitor/absent-required.js +61 -0
- package/dist/pr-monitor/constants.d.ts +7 -0
- package/dist/pr-monitor/constants.js +7 -0
- package/dist/pr-monitor/index.d.ts +2 -1
- package/dist/pr-monitor/index.js +2 -1
- package/dist/pr-monitor/main.js +4 -0
- package/dist/pr-monitor/monitor.js +24 -1
- package/dist/pr-wait-mergeable/classify.js +6 -0
- package/dist/preflight/evaluate.d.ts +4 -0
- package/dist/preflight/evaluate.js +35 -1
- package/dist/preflight/index.d.ts +4 -0
- package/dist/preflight/index.js +2 -0
- package/dist/preflight/intended-placement.d.ts +62 -0
- package/dist/preflight/intended-placement.js +275 -0
- package/dist/preflight/project-invariants-gate.d.ts +29 -0
- package/dist/preflight/project-invariants-gate.js +82 -0
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
- package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
- package/dist/product-first-done-gate/acceptance.d.ts +7 -4
- package/dist/product-first-done-gate/acceptance.js +79 -8
- package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
- package/dist/product-first-done-gate/empty-resolution.js +12 -1
- package/dist/product-first-done-gate/evaluate.d.ts +12 -0
- package/dist/product-first-done-gate/evaluate.js +254 -31
- package/dist/product-first-done-gate/index.d.ts +2 -0
- package/dist/product-first-done-gate/index.js +1 -0
- package/dist/release/build-dist.d.ts +101 -0
- package/dist/release/build-dist.js +428 -46
- package/dist/run-summary/emit.d.ts +16 -10
- package/dist/run-summary/emit.js +26 -15
- package/dist/run-summary/index.d.ts +3 -2
- package/dist/run-summary/index.js +2 -1
- package/dist/run-summary/session-id.d.ts +21 -0
- package/dist/run-summary/session-id.js +92 -0
- package/dist/run-summary/types.d.ts +29 -4
- package/dist/run-summary/types.js +1 -0
- package/dist/scope/acceptance-evidence.d.ts +19 -7
- package/dist/scope/acceptance-evidence.js +38 -11
- package/dist/scope/delivery-evidence.d.ts +3 -3
- package/dist/scope/delivery-evidence.js +22 -27
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/parent-lineage.js +7 -6
- package/dist/scope/project-invariant-coverage.d.ts +54 -0
- package/dist/scope/project-invariant-coverage.js +322 -0
- package/dist/scope/transition.js +1 -1
- package/dist/scope-provenance/compare-intent.d.ts +27 -0
- package/dist/scope-provenance/compare-intent.js +233 -0
- package/dist/scope-provenance/digest.d.ts +18 -1
- package/dist/scope-provenance/digest.js +16 -11
- package/dist/scope-provenance/evaluate.d.ts +5 -1
- package/dist/scope-provenance/evaluate.js +64 -4
- package/dist/scope-provenance/extract-intent.d.ts +43 -0
- package/dist/scope-provenance/extract-intent.js +262 -0
- package/dist/scope-provenance/index.d.ts +7 -1
- package/dist/scope-provenance/index.js +7 -1
- package/dist/scope-provenance/intent-digest.d.ts +12 -0
- package/dist/scope-provenance/intent-digest.js +33 -0
- package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
- package/dist/scope-provenance/intent-evaluate.js +149 -0
- package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
- package/dist/scope-provenance/json-tokenizer.js +225 -0
- package/dist/scope-provenance/known-machine.d.ts +38 -0
- package/dist/scope-provenance/known-machine.js +157 -0
- package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
- package/dist/scope-provenance/mint-artifacts.js +425 -0
- package/dist/session/ac-pass-banking.d.ts +4 -36
- package/dist/session/ac-pass-banking.js +30 -77
- package/dist/session/ac-pass-reuse.d.ts +53 -0
- package/dist/session/ac-pass-reuse.js +95 -0
- package/dist/session/git.js +19 -4
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +4 -0
- package/dist/session/occupancy-stress-worker.d.ts +7 -0
- package/dist/session/occupancy-stress-worker.js +36 -0
- package/dist/session/occupancy.d.ts +97 -0
- package/dist/session/occupancy.js +422 -0
- package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
- package/dist/session/product-state-hash.d.ts +26 -0
- package/dist/session/product-state-hash.js +683 -0
- package/dist/session/session-ready.d.ts +2 -0
- package/dist/session/session-ready.js +52 -30
- package/dist/session/session-start.d.ts +7 -0
- package/dist/session/session-start.js +71 -7
- package/dist/session/verify-ac-session-cache.d.ts +42 -0
- package/dist/session/verify-ac-session-cache.js +126 -0
- package/dist/session/verify-session-ritual.d.ts +5 -0
- package/dist/session/verify-session-ritual.js +9 -0
- package/dist/slash/openclaw-deposit.js +13 -9
- package/dist/slice/constants.d.ts +2 -2
- package/dist/slice/constants.js +2 -2
- package/dist/slice/existing.js +9 -3
- package/dist/slice/lock.d.ts +16 -1
- package/dist/slice/lock.js +137 -11
- package/dist/story-ready/evaluate.d.ts +2 -0
- package/dist/story-ready/evaluate.js +13 -0
- package/dist/swarm/complete-cohort.d.ts +3 -0
- package/dist/swarm/complete-cohort.js +59 -11
- package/dist/swarm/finalize-cohort.js +33 -2
- package/dist/swarm/launch.d.ts +22 -0
- package/dist/swarm/launch.js +155 -0
- package/dist/swarm/routing-set-cli.d.ts +11 -1
- package/dist/swarm/routing-set-cli.js +13 -3
- package/dist/swarm/routing-verify.d.ts +12 -0
- package/dist/swarm/routing-verify.js +59 -20
- package/dist/swarm/routing.d.ts +14 -0
- package/dist/swarm/routing.js +53 -3
- package/dist/telemetry-coverage/fake-trial.js +10 -0
- package/dist/telemetry-coverage/kinds.js +2 -0
- package/dist/triage/actions/index.js +4 -1
- package/dist/ts-check-lane/index.d.ts +3 -0
- package/dist/ts-check-lane/index.js +2 -0
- package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
- package/dist/ts-check-lane/progress-reporter.js +38 -0
- package/dist/ts-check-lane/progress.d.ts +32 -0
- package/dist/ts-check-lane/progress.js +57 -0
- package/dist/ts-check-lane/run-lane.d.ts +3 -1
- package/dist/ts-check-lane/run-lane.js +8 -6
- package/dist/validate-content/validate-links.d.ts +8 -0
- package/dist/validate-content/validate-links.js +9 -15
- package/dist/value/feedback-file.js +12 -2
- package/dist/vbrief-build/index.d.ts +1 -0
- package/dist/vbrief-build/index.js +1 -0
- package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
- package/dist/vbrief-build/project-invariants-io.js +41 -0
- package/dist/vbrief-reconcile/graph.js +18 -1
- package/dist/vbrief-reconcile/index.d.ts +1 -1
- package/dist/vbrief-reconcile/index.js +1 -1
- package/dist/vbrief-reconcile/types.d.ts +13 -0
- package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
- package/dist/vbrief-reconcile/umbrellas.js +261 -26
- package/dist/vbrief-validate/index.d.ts +1 -1
- package/dist/vbrief-validate/index.js +1 -1
- package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
- package/dist/vbrief-validate/plan-hooks.js +68 -0
- package/dist/verify-ac/evaluate.d.ts +7 -1
- package/dist/verify-ac/evaluate.js +34 -26
- package/dist/verify-ac/flag.d.ts +4 -0
- package/dist/verify-ac/flag.js +16 -0
- package/dist/verify-ac/index.d.ts +2 -2
- package/dist/verify-ac/index.js +2 -2
- package/dist/verify-source/contained-writes.js +2 -0
- package/dist/verify-source/verify-stubs.d.ts +15 -1
- package/dist/verify-source/verify-stubs.js +20 -16
- package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
- package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
- package/dist/xbrief-migrate/migrate-project.js +2 -5
- package/package.json +3 -3
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* non-product-dirs.ts — one source of truth for the "not product source"
|
|
3
|
+
* directory basenames shared by every Directive filesystem walk (#3487).
|
|
4
|
+
*
|
|
5
|
+
* Directive shipped four independently hand-maintained exclusion sets:
|
|
6
|
+
*
|
|
7
|
+
* - `EXCLUDE_DIRS` — `verify-source/verify-stubs.ts`
|
|
8
|
+
* - `EXCLUDE_DIRS` — `validate-content/validate-links.ts`
|
|
9
|
+
* - `SKIP_DIRS` — `codebase/default-extractor.ts`
|
|
10
|
+
* - `DEFAULT_EXCLUDES` — `release/build-dist.ts`
|
|
11
|
+
*
|
|
12
|
+
* They drifted from each other, which is exactly how #2953 added
|
|
13
|
+
* `.deft-scratch` / `swarm-worktrees` to some walks and missed the
|
|
14
|
+
* `verify-stubs` walk (#3481), and how none of them ever learned about agent
|
|
15
|
+
* working directories even though Directive ships first-class support for
|
|
16
|
+
* several agent hosts. Because `@deftai/directive-core` is published, a
|
|
17
|
+
* consumer cannot patch any of the four.
|
|
18
|
+
*
|
|
19
|
+
* `NON_PRODUCT_DIRS` is the common core each walk now extends. It carries only
|
|
20
|
+
* basenames that are never product source in any repo: VCS metadata,
|
|
21
|
+
* dependency trees, build output, tool caches, and agent working directories.
|
|
22
|
+
* Walk-specific entries (`tests`, `specs`, `.planning`, `htmlcov`, …) stay with
|
|
23
|
+
* the walk that needs them — this constant is a floor, not a ceiling.
|
|
24
|
+
*
|
|
25
|
+
* Adding a directory here is a one-line change all four walks pick up, and
|
|
26
|
+
* `non-product-dirs.test.ts` fails if any of them stops agreeing.
|
|
27
|
+
*
|
|
28
|
+
* Refs #3487, #3481, #2953, #2954, #1656.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Swarm / agent scratch worktree roots (#2953, #1656).
|
|
32
|
+
*
|
|
33
|
+
* `.deft-scratch` is the current layout (`.deft-scratch/worktrees/<story-id>`,
|
|
34
|
+
* built by `swarm/launch.ts`); `swarm-worktrees` is the pre-`.deft-scratch`
|
|
35
|
+
* name, kept for repos that still carry one.
|
|
36
|
+
*/
|
|
37
|
+
export const AGENT_SCRATCH_DIRS = [".deft-scratch", "swarm-worktrees"];
|
|
38
|
+
/**
|
|
39
|
+
* Agent-host directories that hold host *working* state rather than product
|
|
40
|
+
* source (#3487).
|
|
41
|
+
*
|
|
42
|
+
* Only names with evidence of a real working directory belong here. Directive
|
|
43
|
+
* knows several host directories (`.claude`, `.cursor`, `.grok`, `.codex`,
|
|
44
|
+
* `.github/skills`, `.agents`), but most of them hold nothing except
|
|
45
|
+
* Directive's own committed deposits — `commands.md` § Native multi-host
|
|
46
|
+
* registration (LockedDecision L8) says to *commit* those, so blanket-excluding
|
|
47
|
+
* them would hide committed team surface for no measured gain.
|
|
48
|
+
*
|
|
49
|
+
* `.claude` is different in kind: the Claude Code host keeps live session state
|
|
50
|
+
* in the same directory it keeps config — `settings.local.json`, and agent
|
|
51
|
+
* worktrees under `.claude/worktrees/<agent-id>`, each a full nested checkout.
|
|
52
|
+
* Three such worktrees added ~11,000 walkable entries to this repo
|
|
53
|
+
* (9,128 → 20,393) that no Directive walk skipped. Its Directive-managed
|
|
54
|
+
* contents (`.claude/settings.json`, `.claude/commands/*.md`,
|
|
55
|
+
* `.claude/skills/`) are deposits generated from `content/`, which every walk
|
|
56
|
+
* still visits at the authoring source.
|
|
57
|
+
*
|
|
58
|
+
* Deliberately **not** listed:
|
|
59
|
+
* - `.openclaw` — there is no project-level `.openclaw/`; the host state dir
|
|
60
|
+
* is `~/.openclaw` in `$HOME` (see `doctor/openclaw-skills.ts`), and
|
|
61
|
+
* inventing a project one is explicitly forbidden by
|
|
62
|
+
* `content/docs/openclaw-agent-host.md` and asserted against in
|
|
63
|
+
* `slash/openclaw-adapter.test.ts`.
|
|
64
|
+
* - `.cursor`, `.grok`, `.codex`, `.github` — committed Directive deposits
|
|
65
|
+
* (`.cursor/hooks.json`, `.grok/commands/`, `.codex/prompts/`,
|
|
66
|
+
* `.github/skills/`) with no known working/worktree subtree. No evidence
|
|
67
|
+
* of a scratch convention was found for any of them.
|
|
68
|
+
*/
|
|
69
|
+
export const AGENT_HOST_WORKING_DIRS = [".claude"];
|
|
70
|
+
/**
|
|
71
|
+
* Directory basenames no Directive walk should descend into.
|
|
72
|
+
*
|
|
73
|
+
* Every walk extends this set; none may shrink it. Keep entries to basenames
|
|
74
|
+
* that cannot be product source in any repo — a name that is merely *usually*
|
|
75
|
+
* uninteresting to one gate (`tests`, `scripts`, `specs`) belongs to that
|
|
76
|
+
* walk's own set, not here.
|
|
77
|
+
*/
|
|
78
|
+
export const NON_PRODUCT_DIRS = new Set([
|
|
79
|
+
// Version-control metadata.
|
|
80
|
+
".git",
|
|
81
|
+
// Dependency trees and language virtualenvs.
|
|
82
|
+
"node_modules",
|
|
83
|
+
".venv",
|
|
84
|
+
// Build output and operator backups.
|
|
85
|
+
"dist",
|
|
86
|
+
"backup",
|
|
87
|
+
// Tool caches.
|
|
88
|
+
"__pycache__",
|
|
89
|
+
".mypy_cache",
|
|
90
|
+
".pytest_cache",
|
|
91
|
+
".ruff_cache",
|
|
92
|
+
// Agent working directories.
|
|
93
|
+
...AGENT_SCRATCH_DIRS,
|
|
94
|
+
...AGENT_HOST_WORKING_DIRS,
|
|
95
|
+
]);
|
|
96
|
+
//# sourceMappingURL=non-product-dirs.js.map
|
|
@@ -21,7 +21,9 @@ export type HookDecisionCode = "session-start" | "session-start-disabled" | "ses
|
|
|
21
21
|
/** Enforcement skipped: root `.deft-directive-disable` test kill-switch (#3039). */
|
|
22
22
|
| "directive-disabled" | "session-compact-rearm" | "session-compact-rearm-degraded" | "session-compact-noop" | "not-direct-write" | "invalid-input"
|
|
23
23
|
/** Host closed stdin with zero bytes — integration failure, not a policy gate (#2864). */
|
|
24
|
-
| "stdin-empty" | "ritual-not-ready"
|
|
24
|
+
| "stdin-empty" | "ritual-not-ready"
|
|
25
|
+
/** Product-path write while another live session occupies this worktree (#3433). */
|
|
26
|
+
| "occupancy-occupied" | "scope-not-ready" | "write-propose-ready"
|
|
25
27
|
/** Allowlisted assist/scratch write without active xBRIEF (#1802). */
|
|
26
28
|
| "write-assist-scratch-ready" | "write-ready" | "read-only-deny" | "spawn-explore-ready"
|
|
27
29
|
/** Non-lifecycle assist/docs spawn allowed without active xBRIEF (#3080). */
|
package/dist/hooks/dispatcher.js
CHANGED
|
@@ -10,6 +10,7 @@ import { classifyMcpTool, DEFAULT_RUNTIME_AUTHORITY_POLICY, evaluateRuntimeAutho
|
|
|
10
10
|
import { loadStoryWriteFenceFromPath, resolveWriteFence } from "../policy/write-fence.js";
|
|
11
11
|
import { appendSoftAgentsRebindToMessage, decisionCarriesSoftAgentsRebind, formatSoftAgentsRebindChecklist, } from "../session/compact-ritual.js";
|
|
12
12
|
import { detectBranch } from "../session/git.js";
|
|
13
|
+
import { evaluateOccupancyWriteGate } from "../session/occupancy.js";
|
|
13
14
|
import { emitSessionRitualBlockedProcessCost } from "../session/process-cost.js";
|
|
14
15
|
import { markRitualStaleAfterCompact } from "../session/ritual-sentinel.js";
|
|
15
16
|
import { runSessionStartHookWrite } from "../session/session-start-hook.js";
|
|
@@ -598,6 +599,19 @@ function inspectMutationGates(input, toolName, seams, options) {
|
|
|
598
599
|
return deny(input, "ritual-not-ready", toolName, `Directive could not inspect the gated session ritual: ${String(cause)}. ` +
|
|
599
600
|
formatRitualRecoveryInstruction("cold"));
|
|
600
601
|
}
|
|
602
|
+
const occupancyGate = isSpawnTool(toolName)
|
|
603
|
+
? { allow: true, message: null, occupant: null }
|
|
604
|
+
: evaluateOccupancyWriteGate(projectRoot, { env: environ });
|
|
605
|
+
if (!occupancyGate.allow && occupancyGate.message !== null) {
|
|
606
|
+
const ritualNote = ritual.code !== 0 ? ` Also ritual-not-ready: ${ritual.message}` : "";
|
|
607
|
+
emitSessionRitualBlockedProcessCost({
|
|
608
|
+
toolName,
|
|
609
|
+
code: "occupancy-occupied",
|
|
610
|
+
recoveryTier: ritual.recoveryTier === "rearm" ? "rearm" : "cold",
|
|
611
|
+
detail: occupancyGate.message,
|
|
612
|
+
}, { projectRoot });
|
|
613
|
+
return deny(input, "occupancy-occupied", toolName, `Directive denied ${toolName}: ${occupancyGate.message}${ritualNote}`);
|
|
614
|
+
}
|
|
601
615
|
if (ritual.code !== 0) {
|
|
602
616
|
// #2992: prefer re-arm recovery when age/compact stale; cold when bind invalid.
|
|
603
617
|
const recoveryTier = ritual.recoveryTier === "rearm" ? "rearm" : "cold";
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export * as events from "./events/attribution-ledger.js";
|
|
|
30
30
|
export * from "./forward-coverage/evaluate.js";
|
|
31
31
|
export * as freshness from "./freshness/index.js";
|
|
32
32
|
export { ContainedWriteError, ContainedWriteErrorCode, type ContainedWriteInput, type ContainedWriteMode, type ContainedWriteResult, containedWrite, resolveContainedTarget, } from "./fs/contained-write.js";
|
|
33
|
+
export { AGENT_HOST_WORKING_DIRS, AGENT_SCRATCH_DIRS, NON_PRODUCT_DIRS, } from "./fs/non-product-dirs.js";
|
|
33
34
|
export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
|
|
34
35
|
export * as handoffEvidence from "./handoff-evidence/index.js";
|
|
35
36
|
export * as intake from "./intake/index.js";
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,8 @@ export * from "./forward-coverage/evaluate.js";
|
|
|
31
31
|
export * as freshness from "./freshness/index.js";
|
|
32
32
|
// #2951 Phase 1: contained-write API for product sinks.
|
|
33
33
|
export { ContainedWriteError, ContainedWriteErrorCode, containedWrite, resolveContainedTarget, } from "./fs/contained-write.js";
|
|
34
|
+
// #3487: single source of truth for walk exclusions, shared by every gate.
|
|
35
|
+
export { AGENT_HOST_WORKING_DIRS, AGENT_SCRATCH_DIRS, NON_PRODUCT_DIRS, } from "./fs/non-product-dirs.js";
|
|
34
36
|
export { assertDestinationNotSymlink, assertDirectoryNotSymlink, assertProjectionContained, assertWriteTargetSafe, PROJECTION_CONTAINMENT_REFUSED_EXIT_CODE, ProjectionContainmentError, walkDirectoryRejectSymlinks, } from "./fs/projection-containment.js";
|
|
35
37
|
export * as handoffEvidence from "./handoff-evidence/index.js";
|
|
36
38
|
export * as intake from "./intake/index.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { assertDepositContained } from "../deposit/contain.js";
|
|
4
|
-
import { containedWrite } from "../fs/contained-write.js";
|
|
4
|
+
import { containedRemove, containedRename, containedWrite } from "../fs/contained-write.js";
|
|
5
5
|
import { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
|
|
6
6
|
import { isHostHookDepositEnabled, loadHostHooksPolicyFromProject, } from "../policy/host-hooks.js";
|
|
7
7
|
export { DIRECT_WRITE_HOOK_MATCHER, MCP_HOOK_MATCHER, SHELL_HOOK_MATCHER, SPAWN_HOOK_MATCHER, } from "../hooks/tools.js";
|
|
@@ -217,7 +217,7 @@ function mergeCursorConfig(config, path) {
|
|
|
217
217
|
];
|
|
218
218
|
return { ...config, version: 1, hooks };
|
|
219
219
|
}
|
|
220
|
-
function writeJsonIfChanged(projectRoot, path, payload) {
|
|
220
|
+
function writeJsonIfChanged(projectRoot, path, payload, kind) {
|
|
221
221
|
const next = `${JSON.stringify(payload, null, 2)}\n`;
|
|
222
222
|
if (existsSync(path) && readFileSync(path, "utf8") === next)
|
|
223
223
|
return false;
|
|
@@ -231,12 +231,18 @@ function writeJsonIfChanged(projectRoot, path, payload) {
|
|
|
231
231
|
target: temporary,
|
|
232
232
|
data: next,
|
|
233
233
|
mode: "replace",
|
|
234
|
+
mutation: { kind, path },
|
|
235
|
+
});
|
|
236
|
+
containedRename({
|
|
237
|
+
root: resolve(projectRoot),
|
|
238
|
+
from: temporary,
|
|
239
|
+
to: path,
|
|
240
|
+
mutation: false,
|
|
234
241
|
});
|
|
235
|
-
renameSync(temporary, path);
|
|
236
242
|
}
|
|
237
243
|
catch (err) {
|
|
238
244
|
try {
|
|
239
|
-
|
|
245
|
+
containedRemove({ root: resolve(projectRoot), target: temporary, mutation: false });
|
|
240
246
|
}
|
|
241
247
|
catch {
|
|
242
248
|
/* best-effort cleanup */
|
|
@@ -298,7 +304,7 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
|
|
|
298
304
|
for (const item of prepared) {
|
|
299
305
|
if (item.mode === "skip")
|
|
300
306
|
continue;
|
|
301
|
-
if (writeJsonIfChanged(projectRoot, item.absolute, item.payload)) {
|
|
307
|
+
if (writeJsonIfChanged(projectRoot, item.absolute, item.payload, item.mode === "strip" ? "stripped" : "wrote")) {
|
|
302
308
|
if (item.mode === "strip")
|
|
303
309
|
strippedPaths.push(item.path);
|
|
304
310
|
else
|
|
@@ -309,29 +315,25 @@ export function writeAgentHookDeposit(projectRoot, io = { printf: () => undefine
|
|
|
309
315
|
".cursor/hooks/deft-cursor-hook-adapter.mjs",
|
|
310
316
|
".cursor/hooks/deft-cursor-hook-adapter.test.mjs",
|
|
311
317
|
];
|
|
312
|
-
let
|
|
318
|
+
let adaptersRemoved = 0;
|
|
313
319
|
for (const relative of legacyAdapterPaths) {
|
|
314
320
|
const absolute = join(projectRoot, relative);
|
|
315
321
|
assertDepositContained(projectRoot, absolute);
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
removedLegacyAdapter = true;
|
|
322
|
+
if (containedRemove({ root: projectRoot, target: absolute }).removed) {
|
|
323
|
+
adaptersRemoved += 1;
|
|
319
324
|
}
|
|
320
325
|
}
|
|
321
|
-
if (removedLegacyAdapter && !changedPaths.includes(AGENT_HOOK_PATHS[2])) {
|
|
322
|
-
changedPaths.push(AGENT_HOOK_PATHS[2]);
|
|
323
|
-
}
|
|
324
326
|
if (changedPaths.length > 0) {
|
|
325
327
|
io.printf(`Installed Directive agent hooks: ${changedPaths.join(", ")}\n`);
|
|
326
328
|
}
|
|
327
329
|
if (strippedPaths.length > 0) {
|
|
328
330
|
io.printf(`Removed Directive-managed agent hooks (plan.policy.hostHooks opt-out): ${strippedPaths.join(", ")}\n`);
|
|
329
331
|
}
|
|
330
|
-
if (changedPaths.length === 0 && strippedPaths.length === 0) {
|
|
332
|
+
if (changedPaths.length === 0 && strippedPaths.length === 0 && adaptersRemoved === 0) {
|
|
331
333
|
io.printf("Directive agent hooks already current.\n");
|
|
332
334
|
}
|
|
333
335
|
return {
|
|
334
|
-
changed: changedPaths.length + strippedPaths.length > 0,
|
|
336
|
+
changed: changedPaths.length + strippedPaths.length + adaptersRemoved > 0,
|
|
335
337
|
changedPaths: [...changedPaths, ...strippedPaths],
|
|
336
338
|
};
|
|
337
339
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded python3 pin/lock checker for deposited deft-core-guard (#3193 / #3427).
|
|
3
|
+
* Twin of cmd/deft-install/core_guard_pin_content.embed — keep byte-identical.
|
|
4
|
+
* Not a .py file; CodeQL default setup must not see Python here.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CORE_GUARD_PIN_CONTENT_PYTHON: string;
|
|
7
|
+
//# sourceMappingURL=core-guard-pin-content.d.ts.map
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded python3 pin/lock checker for deposited deft-core-guard (#3193 / #3427).
|
|
3
|
+
* Twin of cmd/deft-install/core_guard_pin_content.embed — keep byte-identical.
|
|
4
|
+
* Not a .py file; CodeQL default setup must not see Python here.
|
|
5
|
+
*/
|
|
6
|
+
export const CORE_GUARD_PIN_CONTENT_PYTHON = [
|
|
7
|
+
"# Content-aware pin unit for deposited deft-core-guard (#3193).",
|
|
8
|
+
'# Invoked as: python3 - "$BASE_SHA" "$HEAD_SHA"',
|
|
9
|
+
"# Mirrors packages/core/src/init-deposit/hygiene.ts isUpgradePinPathContentAllowed.",
|
|
10
|
+
"from __future__ import annotations",
|
|
11
|
+
"",
|
|
12
|
+
"import json",
|
|
13
|
+
"import re",
|
|
14
|
+
"import subprocess",
|
|
15
|
+
"import sys",
|
|
16
|
+
"",
|
|
17
|
+
"_Q = chr(39) + chr(34) # ' and \" without raw-string quote pain",
|
|
18
|
+
"",
|
|
19
|
+
"",
|
|
20
|
+
"def git_show(sha, path):",
|
|
21
|
+
" try:",
|
|
22
|
+
" return subprocess.check_output(",
|
|
23
|
+
' ["git", "show", f"{sha}:{path}"], text=True, stderr=subprocess.DEVNULL',
|
|
24
|
+
" )",
|
|
25
|
+
" except subprocess.CalledProcessError:",
|
|
26
|
+
' return ""',
|
|
27
|
+
"",
|
|
28
|
+
"",
|
|
29
|
+
"def changed_names(base_sha, head_sha):",
|
|
30
|
+
" out = subprocess.check_output(",
|
|
31
|
+
' ["git", "diff", "--name-only", base_sha, head_sha], text=True',
|
|
32
|
+
" )",
|
|
33
|
+
" return [p for p in out.splitlines() if p]",
|
|
34
|
+
"",
|
|
35
|
+
"",
|
|
36
|
+
"def is_dir_key(n):",
|
|
37
|
+
' return n.startswith("@deftai/directive")',
|
|
38
|
+
"",
|
|
39
|
+
"",
|
|
40
|
+
"DEP_FIELDS = (",
|
|
41
|
+
' "dependencies",',
|
|
42
|
+
' "devDependencies",',
|
|
43
|
+
' "optionalDependencies",',
|
|
44
|
+
' "peerDependencies",',
|
|
45
|
+
")",
|
|
46
|
+
"",
|
|
47
|
+
"",
|
|
48
|
+
"def deep_eq(a, b):",
|
|
49
|
+
" if a is b:",
|
|
50
|
+
" return True",
|
|
51
|
+
" if type(a) is not type(b):",
|
|
52
|
+
" return False",
|
|
53
|
+
" if isinstance(a, dict):",
|
|
54
|
+
" if set(a) != set(b):",
|
|
55
|
+
" return False",
|
|
56
|
+
" return all(deep_eq(a[k], b[k]) for k in a)",
|
|
57
|
+
" if isinstance(a, list):",
|
|
58
|
+
" return len(a) == len(b) and all(deep_eq(x, y) for x, y in zip(a, b))",
|
|
59
|
+
" return a == b",
|
|
60
|
+
"",
|
|
61
|
+
"",
|
|
62
|
+
"def strip_pins(obj):",
|
|
63
|
+
" if not isinstance(obj, dict):",
|
|
64
|
+
" return obj",
|
|
65
|
+
" out = {}",
|
|
66
|
+
" for k, v in obj.items():",
|
|
67
|
+
" if k in DEP_FIELDS and isinstance(v, dict):",
|
|
68
|
+
" out[k] = {dk: dv for dk, dv in v.items() if not is_dir_key(dk)}",
|
|
69
|
+
" else:",
|
|
70
|
+
" out[k] = v",
|
|
71
|
+
" return out",
|
|
72
|
+
"",
|
|
73
|
+
"",
|
|
74
|
+
"def pkg_pin_only(base, head):",
|
|
75
|
+
" try:",
|
|
76
|
+
' b, h = json.loads(base or "{}"), json.loads(head or "{}")',
|
|
77
|
+
" except json.JSONDecodeError:",
|
|
78
|
+
" return False",
|
|
79
|
+
" return deep_eq(strip_pins(b), strip_pins(h))",
|
|
80
|
+
"",
|
|
81
|
+
"",
|
|
82
|
+
"def collect_deps(pkg):",
|
|
83
|
+
" out = {}",
|
|
84
|
+
" if not isinstance(pkg, dict):",
|
|
85
|
+
" return out",
|
|
86
|
+
" for f in DEP_FIELDS:",
|
|
87
|
+
" block = pkg.get(f)",
|
|
88
|
+
" if isinstance(block, dict):",
|
|
89
|
+
" for k, v in block.items():",
|
|
90
|
+
" if isinstance(v, str):",
|
|
91
|
+
" out[k] = v",
|
|
92
|
+
' elif isinstance(v, dict) and isinstance(v.get("version"), str):',
|
|
93
|
+
' out[k] = v["version"]',
|
|
94
|
+
" elif v is not None:",
|
|
95
|
+
" out[k] = str(v)",
|
|
96
|
+
" return out",
|
|
97
|
+
"",
|
|
98
|
+
"",
|
|
99
|
+
"def only_dir_diff(bd, hd):",
|
|
100
|
+
" keys = set(bd) | set(hd)",
|
|
101
|
+
" for k in keys:",
|
|
102
|
+
" if is_dir_key(k):",
|
|
103
|
+
" continue",
|
|
104
|
+
" if bd.get(k) != hd.get(k):",
|
|
105
|
+
" return False",
|
|
106
|
+
" return True",
|
|
107
|
+
"",
|
|
108
|
+
"",
|
|
109
|
+
"def npm_root_deps(lock):",
|
|
110
|
+
' pkgs = lock.get("packages") if isinstance(lock, dict) else None',
|
|
111
|
+
' if isinstance(pkgs, dict) and isinstance(pkgs.get(""), dict):',
|
|
112
|
+
' return collect_deps(pkgs[""])',
|
|
113
|
+
' deps = lock.get("dependencies") if isinstance(lock, dict) else None',
|
|
114
|
+
" out = {}",
|
|
115
|
+
" if isinstance(deps, dict):",
|
|
116
|
+
" for k, v in deps.items():",
|
|
117
|
+
" if isinstance(v, dict):",
|
|
118
|
+
' out[k] = str(v.get("version", v))',
|
|
119
|
+
" elif isinstance(v, str):",
|
|
120
|
+
" out[k] = v",
|
|
121
|
+
" return out",
|
|
122
|
+
"",
|
|
123
|
+
"",
|
|
124
|
+
"def npm_lock_ok(base, head):",
|
|
125
|
+
" try:",
|
|
126
|
+
' b, h = json.loads(base or "{}"), json.loads(head or "{}")',
|
|
127
|
+
" except json.JSONDecodeError:",
|
|
128
|
+
" return False",
|
|
129
|
+
" br, hr = npm_root_deps(b), npm_root_deps(h)",
|
|
130
|
+
" if not only_dir_diff(br, hr):",
|
|
131
|
+
" return False",
|
|
132
|
+
' bp, hp = b.get("packages") or {}, h.get("packages") or {}',
|
|
133
|
+
" if not isinstance(bp, dict):",
|
|
134
|
+
" bp = {}",
|
|
135
|
+
" if not isinstance(hp, dict):",
|
|
136
|
+
" hp = {}",
|
|
137
|
+
" all_keys = set(bp) | set(hp)",
|
|
138
|
+
" for key in all_keys:",
|
|
139
|
+
' if key == "":',
|
|
140
|
+
" continue",
|
|
141
|
+
' if "node_modules/@deftai/directive" in key or "/@deftai/directive/" in key:',
|
|
142
|
+
" continue",
|
|
143
|
+
" if not deep_eq(bp.get(key), hp.get(key)):",
|
|
144
|
+
" return False",
|
|
145
|
+
" # lockfileVersion 1: freeze nested dependencies tree without directive keys",
|
|
146
|
+
" if not bp and not hp:",
|
|
147
|
+
" def strip_dir(v):",
|
|
148
|
+
" if not isinstance(v, dict):",
|
|
149
|
+
" return v",
|
|
150
|
+
" out = {}",
|
|
151
|
+
" for k, val in v.items():",
|
|
152
|
+
" if is_dir_key(k):",
|
|
153
|
+
" continue",
|
|
154
|
+
" if isinstance(val, dict):",
|
|
155
|
+
" nxt = dict(val)",
|
|
156
|
+
' if "dependencies" in val:',
|
|
157
|
+
' nxt["dependencies"] = strip_dir(val["dependencies"])',
|
|
158
|
+
" out[k] = nxt",
|
|
159
|
+
" else:",
|
|
160
|
+
" out[k] = val",
|
|
161
|
+
" return out",
|
|
162
|
+
' if not deep_eq(strip_dir(b.get("dependencies")), strip_dir(h.get("dependencies"))):',
|
|
163
|
+
" return False",
|
|
164
|
+
" return True",
|
|
165
|
+
"",
|
|
166
|
+
"",
|
|
167
|
+
"def unq(s):",
|
|
168
|
+
" s = s.strip()",
|
|
169
|
+
" if len(s) >= 2 and s[0] == s[-1] and s[0] in _Q:",
|
|
170
|
+
" return s[1:-1]",
|
|
171
|
+
" return s",
|
|
172
|
+
"",
|
|
173
|
+
"",
|
|
174
|
+
"def pnpm_root_deps(raw):",
|
|
175
|
+
" out = {}",
|
|
176
|
+
" in_imp = in_root = in_dep = False",
|
|
177
|
+
" cur = None",
|
|
178
|
+
" for line in raw.splitlines():",
|
|
179
|
+
' if re.match(r"^importers:\\s*$", line):',
|
|
180
|
+
" in_imp, in_root, in_dep, cur = True, False, False, None",
|
|
181
|
+
" continue",
|
|
182
|
+
" if not in_imp:",
|
|
183
|
+
" continue",
|
|
184
|
+
' if re.match(r"^[^\\s#]", line) and not line.startswith("importers"):',
|
|
185
|
+
" break",
|
|
186
|
+
' if re.match(r"^ {2}(?:\\.|\'\\.\'|\\"\\.\\"):\\s*$", line):',
|
|
187
|
+
" in_root, in_dep, cur = True, False, None",
|
|
188
|
+
" continue",
|
|
189
|
+
' if in_root and re.match(r"^ {2}\\S", line) and not re.match(r"^ {2}\\.", line):',
|
|
190
|
+
" in_root, in_dep, cur = False, False, None",
|
|
191
|
+
" continue",
|
|
192
|
+
" if not in_root:",
|
|
193
|
+
" continue",
|
|
194
|
+
" if re.match(",
|
|
195
|
+
' r"^ {4}(?:dependencies|devDependencies|optionalDependencies|peerDependencies):\\s*$",',
|
|
196
|
+
" line,",
|
|
197
|
+
" ):",
|
|
198
|
+
" in_dep, cur = True, None",
|
|
199
|
+
" continue",
|
|
200
|
+
' if in_dep and re.match(r"^ {4}\\S", line):',
|
|
201
|
+
" in_dep, cur = False, None",
|
|
202
|
+
" continue",
|
|
203
|
+
" if not in_dep:",
|
|
204
|
+
" continue",
|
|
205
|
+
' m = re.match(r"^ {6}(.+?):\\s*$", line)',
|
|
206
|
+
" if m:",
|
|
207
|
+
" cur = unq(m.group(1))",
|
|
208
|
+
" continue",
|
|
209
|
+
" if cur:",
|
|
210
|
+
' vm = re.match(r"^ {8}version:\\s*(.+?)\\s*$", line)',
|
|
211
|
+
" if vm:",
|
|
212
|
+
" out[cur] = unq(vm.group(1))",
|
|
213
|
+
" cur = None",
|
|
214
|
+
" return out",
|
|
215
|
+
"",
|
|
216
|
+
"",
|
|
217
|
+
"def pnpm_packages_by_name(raw):",
|
|
218
|
+
" out = {}",
|
|
219
|
+
" blocks = {}",
|
|
220
|
+
" in_pkg = False",
|
|
221
|
+
" cur = None",
|
|
222
|
+
" buf = []",
|
|
223
|
+
"",
|
|
224
|
+
" def flush():",
|
|
225
|
+
" nonlocal cur, buf",
|
|
226
|
+
" if cur is None:",
|
|
227
|
+
" return",
|
|
228
|
+
' blocks.setdefault(cur, []).append("\\n".join(buf))',
|
|
229
|
+
" cur, buf = None, []",
|
|
230
|
+
"",
|
|
231
|
+
" for line in raw.splitlines():",
|
|
232
|
+
' if re.match(r"^packages:\\s*$", line):',
|
|
233
|
+
" flush()",
|
|
234
|
+
" in_pkg = True",
|
|
235
|
+
" continue",
|
|
236
|
+
" if not in_pkg:",
|
|
237
|
+
" continue",
|
|
238
|
+
' if re.match(r"^[^\\s#]", line) and not line.startswith("packages"):',
|
|
239
|
+
" flush()",
|
|
240
|
+
" break",
|
|
241
|
+
' m = re.match(r"^ {2}(.+?):\\s*$", line)',
|
|
242
|
+
" if m:",
|
|
243
|
+
" flush()",
|
|
244
|
+
" key = unq(m.group(1))",
|
|
245
|
+
' at = key.find("@", 1) if key.startswith("@") else key.find("@")',
|
|
246
|
+
" cur = key[:at] if at > 0 else key",
|
|
247
|
+
" buf = [line]",
|
|
248
|
+
" continue",
|
|
249
|
+
" if cur is not None:",
|
|
250
|
+
" buf.append(line)",
|
|
251
|
+
" flush()",
|
|
252
|
+
" for name, blist in blocks.items():",
|
|
253
|
+
' out[name] = "\\n---\\n".join(sorted(blist))',
|
|
254
|
+
" return out",
|
|
255
|
+
"",
|
|
256
|
+
"",
|
|
257
|
+
"def pnpm_section_by_name(raw, section):",
|
|
258
|
+
" blocks, out = {}, {}",
|
|
259
|
+
" in_sec, cur, buf = False, None, []",
|
|
260
|
+
' sec_re = re.compile(r"^" + re.escape(section) + r":\\s*$")',
|
|
261
|
+
"",
|
|
262
|
+
" def flush():",
|
|
263
|
+
" nonlocal cur, buf",
|
|
264
|
+
" if cur is None:",
|
|
265
|
+
" return",
|
|
266
|
+
' blocks.setdefault(cur, []).append("\\n".join(buf))',
|
|
267
|
+
" cur, buf = None, []",
|
|
268
|
+
"",
|
|
269
|
+
" for line in raw.splitlines():",
|
|
270
|
+
" if sec_re.match(line):",
|
|
271
|
+
" flush()",
|
|
272
|
+
" in_sec = True",
|
|
273
|
+
" continue",
|
|
274
|
+
" if not in_sec:",
|
|
275
|
+
" continue",
|
|
276
|
+
' if re.match(r"^[^\\s#]", line) and not line.startswith(section):',
|
|
277
|
+
" flush()",
|
|
278
|
+
" break",
|
|
279
|
+
' m = re.match(r"^ {2}(.+?):\\s*$", line)',
|
|
280
|
+
" if m:",
|
|
281
|
+
" flush()",
|
|
282
|
+
" key = unq(m.group(1))",
|
|
283
|
+
' at = key.find("@", 1) if key.startswith("@") else key.find("@")',
|
|
284
|
+
" cur = key[:at] if at > 0 else key",
|
|
285
|
+
" buf = [line]",
|
|
286
|
+
" continue",
|
|
287
|
+
" if cur is not None:",
|
|
288
|
+
" buf.append(line)",
|
|
289
|
+
" flush()",
|
|
290
|
+
" for name, blist in blocks.items():",
|
|
291
|
+
' out[name] = "\\n---\\n".join(sorted(blist))',
|
|
292
|
+
" return out",
|
|
293
|
+
"",
|
|
294
|
+
"",
|
|
295
|
+
"def pnpm_ok(base, head):",
|
|
296
|
+
" br, hr = pnpm_root_deps(base), pnpm_root_deps(head)",
|
|
297
|
+
" if not only_dir_diff(br, hr):",
|
|
298
|
+
" return False",
|
|
299
|
+
' for section in ("packages", "snapshots"):',
|
|
300
|
+
" bp, hp = pnpm_section_by_name(base, section), pnpm_section_by_name(head, section)",
|
|
301
|
+
" for name in set(bp) | set(hp):",
|
|
302
|
+
" if is_dir_key(name):",
|
|
303
|
+
" continue",
|
|
304
|
+
" if bp.get(name) != hp.get(name):",
|
|
305
|
+
" return False",
|
|
306
|
+
" return True",
|
|
307
|
+
"",
|
|
308
|
+
"",
|
|
309
|
+
"def yarn_blocks(raw):",
|
|
310
|
+
" blocks = {}",
|
|
311
|
+
" names, buf = [], []",
|
|
312
|
+
"",
|
|
313
|
+
" def flush():",
|
|
314
|
+
" nonlocal names, buf",
|
|
315
|
+
" if not names:",
|
|
316
|
+
" return",
|
|
317
|
+
' body = "\\n".join(buf)',
|
|
318
|
+
" for n in names:",
|
|
319
|
+
" blocks[n] = body",
|
|
320
|
+
" names, buf = [], []",
|
|
321
|
+
"",
|
|
322
|
+
" for line in raw.splitlines():",
|
|
323
|
+
' if line == "" or line.startswith("#"):',
|
|
324
|
+
' if names and line == "":',
|
|
325
|
+
" flush()",
|
|
326
|
+
" continue",
|
|
327
|
+
' if not re.match(r"^\\s", line) and line.endswith(":"):',
|
|
328
|
+
" flush()",
|
|
329
|
+
" header = line[:-1]",
|
|
330
|
+
" names = []",
|
|
331
|
+
' for part in header.split(","):',
|
|
332
|
+
" p = part.strip()",
|
|
333
|
+
" if len(p) >= 2 and p[0] == p[-1] and p[0] in _Q:",
|
|
334
|
+
" p = p[1:-1]",
|
|
335
|
+
' at = p.find("@", 1) if p.startswith("@") else p.find("@")',
|
|
336
|
+
" names.append(p[:at] if at > 0 else p)",
|
|
337
|
+
" buf = [line]",
|
|
338
|
+
" continue",
|
|
339
|
+
" if names:",
|
|
340
|
+
" buf.append(line)",
|
|
341
|
+
" flush()",
|
|
342
|
+
" return blocks",
|
|
343
|
+
"",
|
|
344
|
+
"",
|
|
345
|
+
"def yarn_ok(base, head):",
|
|
346
|
+
" bb, hb = yarn_blocks(base), yarn_blocks(head)",
|
|
347
|
+
" for name in set(bb) | set(hb):",
|
|
348
|
+
" if is_dir_key(name):",
|
|
349
|
+
" continue",
|
|
350
|
+
" if bb.get(name) != hb.get(name):",
|
|
351
|
+
" return False",
|
|
352
|
+
" return True",
|
|
353
|
+
"",
|
|
354
|
+
"",
|
|
355
|
+
"def main(argv: list[str] | None = None) -> int:",
|
|
356
|
+
" args = list(sys.argv[1:] if argv is None else argv)",
|
|
357
|
+
" if len(args) < 2:",
|
|
358
|
+
' print("usage: core_guard_pin_content <base_sha> <head_sha>", file=sys.stderr)',
|
|
359
|
+
" return 2",
|
|
360
|
+
" base_sha, head_sha = args[0], args[1]",
|
|
361
|
+
" paths = changed_names(base_sha, head_sha)",
|
|
362
|
+
' core = [p for p in paths if p == ".deft/core" or p.startswith(".deft/core/")]',
|
|
363
|
+
" if not core:",
|
|
364
|
+
" return 0",
|
|
365
|
+
" checks = {",
|
|
366
|
+
' "package.json": pkg_pin_only,',
|
|
367
|
+
' "package-lock.json": npm_lock_ok,',
|
|
368
|
+
' "pnpm-lock.yaml": pnpm_ok,',
|
|
369
|
+
' "yarn.lock": yarn_ok,',
|
|
370
|
+
" }",
|
|
371
|
+
" bad = []",
|
|
372
|
+
" for path, fn in checks.items():",
|
|
373
|
+
" if path not in paths:",
|
|
374
|
+
" continue",
|
|
375
|
+
" if not fn(git_show(base_sha, path), git_show(head_sha, path)):",
|
|
376
|
+
" bad.append(path)",
|
|
377
|
+
" if bad:",
|
|
378
|
+
" print(",
|
|
379
|
+
' "::error title=deft-core guard (#3193)::package/lock co-travel with "',
|
|
380
|
+
' ".deft/core/** must be @deftai/directive* pin-only + lock follow-through. "',
|
|
381
|
+
' "Rejected: " + ", ".join(bad)',
|
|
382
|
+
" )",
|
|
383
|
+
' print("--- content-rejected pin/lock paths ---")',
|
|
384
|
+
' print("\\n".join(bad))',
|
|
385
|
+
" return 1",
|
|
386
|
+
' print("OK: package/lock content is Directive pin unit (#3193).")',
|
|
387
|
+
" return 0",
|
|
388
|
+
"",
|
|
389
|
+
"",
|
|
390
|
+
'if __name__ == "__main__":',
|
|
391
|
+
" sys.exit(main())",
|
|
392
|
+
].join("\n") + "\n";
|
|
393
|
+
//# sourceMappingURL=core-guard-pin-content.js.map
|
|
@@ -29,7 +29,16 @@ export const CANONICAL_GITIGNORE_BASELINE = [
|
|
|
29
29
|
".deft/.cli/",
|
|
30
30
|
".deft/ritual-state.json",
|
|
31
31
|
".deft/last-session.json",
|
|
32
|
+
".deft/occupancy.json",
|
|
32
33
|
".deft/routing.local.json",
|
|
34
|
+
// Agent-host working state (#3502). Selective, NEVER blanket `.claude/`:
|
|
35
|
+
// `.claude/settings.json`, `.claude/skills/` and `.claude/commands/` are
|
|
36
|
+
// MANAGED DEPOSITS (agent-hooks.ts / skill-discovery-hosts.ts) and must stay
|
|
37
|
+
// trackable -- same #1144 hybrid rule that forbids blanket `.eval/`.
|
|
38
|
+
// Un-ignored agent worktrees leave a permanently dirty tree, which refuses
|
|
39
|
+
// release Step 1 for every consumer that dispatches subagents.
|
|
40
|
+
".claude/worktrees/",
|
|
41
|
+
".claude/settings.local.json",
|
|
33
42
|
// #3282: opt-in default run-summary JSONL at repo root (collectible; must not dirty trees).
|
|
34
43
|
".deft-run-summary.json",
|
|
35
44
|
// Temporary test/local kill-switch — must stay untracked (#3039).
|