@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.
Files changed (193) hide show
  1. package/dist/authz/actions.d.ts +55 -0
  2. package/dist/authz/actions.js +125 -0
  3. package/dist/authz/classify.d.ts +23 -0
  4. package/dist/authz/classify.js +217 -0
  5. package/dist/authz/closed-verb.d.ts +42 -0
  6. package/dist/authz/closed-verb.js +279 -0
  7. package/dist/authz/evaluate.d.ts +41 -0
  8. package/dist/authz/evaluate.js +298 -0
  9. package/dist/authz/index.d.ts +15 -0
  10. package/dist/authz/index.js +15 -0
  11. package/dist/authz/origin.d.ts +28 -0
  12. package/dist/authz/origin.js +64 -0
  13. package/dist/authz/paths.d.ts +6 -0
  14. package/dist/authz/paths.js +19 -0
  15. package/dist/authz/store.d.ts +40 -0
  16. package/dist/authz/store.js +317 -0
  17. package/dist/authz/templates.d.ts +139 -0
  18. package/dist/authz/templates.js +241 -0
  19. package/dist/authz/types.d.ts +111 -0
  20. package/dist/authz/types.js +41 -0
  21. package/dist/authz/verb-classification.d.ts +44 -0
  22. package/dist/authz/verb-classification.js +237 -0
  23. package/dist/branch/evaluate.js +6 -1
  24. package/dist/cache/fetch.js +10 -5
  25. package/dist/cache/io.d.ts +9 -2
  26. package/dist/cache/io.js +30 -10
  27. package/dist/cache/scanner.d.ts +11 -1
  28. package/dist/cache/scanner.js +29 -4
  29. package/dist/cache/task-cache/store.js +11 -7
  30. package/dist/capacity/backfill.js +10 -4
  31. package/dist/category-b-namespace/index.js +10 -4
  32. package/dist/check/gate-lists.js +1 -0
  33. package/dist/codebase/default-extractor.js +3 -0
  34. package/dist/codebase/map.js +11 -4
  35. package/dist/content-contracts/skills/helpers.d.ts +10 -0
  36. package/dist/content-contracts/skills/helpers.js +35 -0
  37. package/dist/deposit/copy-tree.d.ts +19 -1
  38. package/dist/deposit/copy-tree.js +134 -5
  39. package/dist/doctor/doctor-state.js +28 -4
  40. package/dist/doctor/main.d.ts +10 -0
  41. package/dist/doctor/main.js +208 -0
  42. package/dist/doctor/types.d.ts +11 -0
  43. package/dist/escalation/actions.d.ts +63 -0
  44. package/dist/escalation/actions.js +137 -0
  45. package/dist/escalation/index.d.ts +8 -0
  46. package/dist/escalation/index.js +8 -0
  47. package/dist/escalation/paths.d.ts +3 -0
  48. package/dist/escalation/paths.js +10 -0
  49. package/dist/escalation/store.d.ts +17 -0
  50. package/dist/escalation/store.js +172 -0
  51. package/dist/escalation/types.d.ts +73 -0
  52. package/dist/escalation/types.js +43 -0
  53. package/dist/eval/crud-telemetry.js +30 -10
  54. package/dist/eval/health.js +22 -7
  55. package/dist/eval/readback.js +11 -10
  56. package/dist/eval/run.js +32 -16
  57. package/dist/events/attribution-enrichment.js +10 -4
  58. package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
  59. package/dist/finish-loop/directive-finish-loop.js +239 -0
  60. package/dist/finish-loop/grant-gate.d.ts +31 -0
  61. package/dist/finish-loop/grant-gate.js +169 -0
  62. package/dist/finish-loop/index.d.ts +11 -0
  63. package/dist/finish-loop/index.js +11 -0
  64. package/dist/finish-loop/main.d.ts +35 -0
  65. package/dist/finish-loop/main.js +365 -0
  66. package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
  67. package/dist/finish-loop/pr-finish-loop.js +217 -0
  68. package/dist/finish-loop/progress.d.ts +17 -0
  69. package/dist/finish-loop/progress.js +45 -0
  70. package/dist/finish-loop/queue.d.ts +16 -0
  71. package/dist/finish-loop/queue.js +65 -0
  72. package/dist/finish-loop/types.d.ts +52 -0
  73. package/dist/finish-loop/types.js +10 -0
  74. package/dist/fs/contained-write.d.ts +101 -0
  75. package/dist/fs/contained-write.js +281 -0
  76. package/dist/fs/projection-containment.d.ts +18 -0
  77. package/dist/fs/projection-containment.js +40 -0
  78. package/dist/hooks/classify/classify.d.ts +10 -0
  79. package/dist/hooks/classify/classify.js +37 -0
  80. package/dist/hooks/classify/index.d.ts +14 -0
  81. package/dist/hooks/classify/index.js +13 -0
  82. package/dist/hooks/classify/paths.d.ts +22 -0
  83. package/dist/hooks/classify/paths.js +72 -0
  84. package/dist/hooks/classify/payload.d.ts +16 -0
  85. package/dist/hooks/classify/payload.js +45 -0
  86. package/dist/hooks/classify/stdin.d.ts +12 -0
  87. package/dist/hooks/classify/stdin.js +63 -0
  88. package/dist/hooks/classify/tool-name.d.ts +18 -0
  89. package/dist/hooks/classify/tool-name.js +85 -0
  90. package/dist/hooks/classify/types.d.ts +41 -0
  91. package/dist/hooks/classify/types.js +7 -0
  92. package/dist/hooks/dispatcher.d.ts +30 -15
  93. package/dist/hooks/dispatcher.js +326 -134
  94. package/dist/hooks/fixtures/cases.d.ts +44 -0
  95. package/dist/hooks/fixtures/cases.js +595 -0
  96. package/dist/hooks/fixtures/index.d.ts +2 -0
  97. package/dist/hooks/fixtures/index.js +2 -0
  98. package/dist/hooks/index.d.ts +2 -0
  99. package/dist/hooks/index.js +2 -0
  100. package/dist/hooks/tools.d.ts +31 -0
  101. package/dist/hooks/tools.js +74 -0
  102. package/dist/index.d.ts +4 -0
  103. package/dist/index.js +5 -0
  104. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  105. package/dist/init-deposit/agent-hooks.js +65 -10
  106. package/dist/init-deposit/gitignore.js +11 -3
  107. package/dist/init-deposit/headless-manifest.js +12 -4
  108. package/dist/init-deposit/hygiene.d.ts +16 -0
  109. package/dist/init-deposit/hygiene.js +26 -0
  110. package/dist/init-deposit/init-dispatch.js +28 -0
  111. package/dist/init-deposit/migrate.d.ts +1 -1
  112. package/dist/init-deposit/migrate.js +13 -3
  113. package/dist/init-deposit/prettierignore.js +12 -5
  114. package/dist/init-deposit/refresh.js +38 -7
  115. package/dist/init-deposit/scaffold.js +42 -29
  116. package/dist/init-deposit/xbrief-projections.js +17 -10
  117. package/dist/intake/candidates-log.js +46 -35
  118. package/dist/intake/github-body-cli.d.ts +6 -0
  119. package/dist/intake/github-body-cli.js +17 -0
  120. package/dist/intake/github-body.d.ts +46 -3
  121. package/dist/intake/github-body.js +191 -20
  122. package/dist/intake/issue-emit.d.ts +1 -0
  123. package/dist/intake/issue-emit.js +41 -23
  124. package/dist/intake/issue-ingest.js +11 -2
  125. package/dist/lifecycle/event-detect.js +12 -4
  126. package/dist/lifecycle/events.js +29 -20
  127. package/dist/lifecycle/lifecycle-hygiene.js +9 -3
  128. package/dist/packs/pack-render.d.ts +33 -0
  129. package/dist/packs/pack-render.js +172 -13
  130. package/dist/packs/quarantine-ext.d.ts +10 -0
  131. package/dist/packs/quarantine-ext.js +26 -2
  132. package/dist/plan-sequence/store.js +11 -4
  133. package/dist/policy/hotfix-criteria.d.ts +79 -0
  134. package/dist/policy/hotfix-criteria.js +197 -0
  135. package/dist/policy/index.d.ts +5 -0
  136. package/dist/policy/index.js +32 -1
  137. package/dist/policy/intent-ceiling.d.ts +79 -0
  138. package/dist/policy/intent-ceiling.js +181 -0
  139. package/dist/policy/no-deft-directive.d.ts +59 -0
  140. package/dist/policy/no-deft-directive.js +110 -0
  141. package/dist/policy/org-force-on-migration.d.ts +52 -0
  142. package/dist/policy/org-force-on-migration.js +269 -27
  143. package/dist/policy/require-human-merge.d.ts +75 -0
  144. package/dist/policy/require-human-merge.js +324 -0
  145. package/dist/policy/resolve.js +17 -6
  146. package/dist/policy/runtime-authority.d.ts +56 -2
  147. package/dist/policy/runtime-authority.js +297 -3
  148. package/dist/policy/write-fence.d.ts +78 -0
  149. package/dist/policy/write-fence.js +164 -0
  150. package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
  151. package/dist/pr-wait-mergeable/cascade.js +28 -0
  152. package/dist/preflight/evaluate.js +10 -0
  153. package/dist/product-signal/consent.js +21 -5
  154. package/dist/product-signal/submit.js +22 -12
  155. package/dist/release/build-dist-runner.js +5 -2
  156. package/dist/release/build-dist.d.ts +19 -1
  157. package/dist/release/build-dist.js +50 -2
  158. package/dist/release/native-steps.js +7 -2
  159. package/dist/release-publish/pipeline.d.ts +13 -0
  160. package/dist/release-publish/pipeline.js +46 -1
  161. package/dist/scope/audit-log.js +19 -24
  162. package/dist/scope/decompose.js +10 -5
  163. package/dist/scope/decomposed-refs.js +18 -3
  164. package/dist/scope/demote.js +9 -2
  165. package/dist/scope/undo.js +9 -2
  166. package/dist/session/release-availability.js +26 -6
  167. package/dist/session/ritual-sentinel.js +19 -12
  168. package/dist/session/session-start-hook.d.ts +3 -0
  169. package/dist/session/session-start-hook.js +15 -0
  170. package/dist/session/session-start.js +37 -0
  171. package/dist/staleness-tickler/state.js +26 -6
  172. package/dist/swarm/launch.js +13 -3
  173. package/dist/swarm/routing.js +9 -3
  174. package/dist/task-surface/index.js +24 -9
  175. package/dist/triage/bootstrap/gitignore.js +53 -10
  176. package/dist/triage/cache-path.js +10 -3
  177. package/dist/triage/welcome/summary.js +11 -4
  178. package/dist/triage/welcome/writers.js +45 -16
  179. package/dist/validate-content/validate-links.js +5 -0
  180. package/dist/value/readback.js +11 -6
  181. package/dist/vbrief-activate/activate.js +12 -4
  182. package/dist/vbrief-build/project-definition-io.js +14 -6
  183. package/dist/verify-source/contained-writes.d.ts +59 -0
  184. package/dist/verify-source/contained-writes.js +272 -0
  185. package/dist/verify-source/cursor-tier1.js +7 -2
  186. package/dist/verify-source/index.d.ts +1 -0
  187. package/dist/verify-source/index.js +1 -0
  188. package/dist/verify-source/openclaw-tier1.js +19 -1
  189. package/dist/verify-source/verify-stubs.js +3 -0
  190. package/dist/xbrief-migrate/migrate-project.js +26 -12
  191. package/dist/xbrief-migrate/transforms.d.ts +4 -2
  192. package/dist/xbrief-migrate/transforms.js +37 -14
  193. package/package.json +16 -3
@@ -1,6 +1,8 @@
1
1
  import { FIELD_HOST_HOOKS, FIELD_HOST_HOOKS_CLI_ALIAS, inspectHostHooks } from "./host-hooks.js";
2
+ import { FIELD_HOTFIX_CRITERIA, FIELD_HOTFIX_CRITERIA_CLI_ALIAS, inspectHotfixCriteria, } from "./hotfix-criteria.js";
2
3
  import { readPlanPolicy } from "./plan-extensions.js";
3
4
  import { FIELD_PRODUCT_SIGNAL, FIELD_PRODUCT_SIGNAL_CLI_ALIAS, inspectProductSignal, } from "./product-signal.js";
5
+ import { FIELD_REQUIRE_HUMAN_MERGE, FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS, inspectRequireHumanMerge, } from "./require-human-merge.js";
4
6
  import { coerceLegacyNarrative, LEGACY_NARRATIVE_KEY, loadProjectDefinition } from "./resolve.js";
5
7
  import { FIELD_RUNTIME_AUTHORITY, FIELD_RUNTIME_AUTHORITY_CLI_ALIAS, inspectRuntimeAuthority, } from "./runtime-authority.js";
6
8
  import { FIELD_STALENESS_TICKLER, FIELD_STALENESS_TICKLER_CLI_ALIAS, inspectStalenessTickler, } from "./staleness-tickler.js";
@@ -12,16 +14,21 @@ export * from "./capacity.js";
12
14
  export * from "./decisions.js";
13
15
  export * from "./disclosure.js";
14
16
  export * from "./host-hooks.js";
17
+ export * from "./hotfix-criteria.js";
18
+ export * from "./intent-ceiling.js";
19
+ export * from "./no-deft-directive.js";
15
20
  export * from "./org-force-on-migration.js";
16
21
  export * from "./plan-extensions.js";
17
22
  export * from "./policy-invocation.js";
18
23
  export * from "./product-signal.js";
24
+ export * from "./require-human-merge.js";
19
25
  export * from "./resolve.js";
20
26
  export * from "./runtime-authority.js";
21
27
  export * from "./staleness-tickler.js";
22
28
  export * from "./value-feedback.js";
23
29
  export * from "./value-feedback-autoenable.js";
24
30
  export * from "./wip.js";
31
+ export * from "./write-fence.js";
25
32
  export const FIELD_ALLOW_DIRECT_COMMITS = "plan.policy.allowDirectCommitsToMaster";
26
33
  export const FIELD_WIP_CAP = "plan.policy.wipCap";
27
34
  export const FIELD_SESSION_RITUAL_STALENESS_HOURS = "plan.policy.sessionRitualStalenessHours";
@@ -271,6 +278,24 @@ function inspectHostHooksField(data) {
271
278
  source: field.source,
272
279
  };
273
280
  }
281
+ function inspectRequireHumanMergeField(data, projectRoot) {
282
+ const field = inspectRequireHumanMerge(data, projectRoot);
283
+ return {
284
+ name: field.name,
285
+ current: field.current,
286
+ default: field.default,
287
+ source: field.source,
288
+ };
289
+ }
290
+ function inspectHotfixCriteriaField(data) {
291
+ const field = inspectHotfixCriteria(data);
292
+ return {
293
+ name: field.name,
294
+ current: field.current,
295
+ default: field.default,
296
+ source: field.source,
297
+ };
298
+ }
274
299
  const REGISTERED_POLICIES = [
275
300
  inspectAllowDirectCommits,
276
301
  inspectWipCap,
@@ -288,6 +313,8 @@ const REGISTERED_POLICIES = [
288
313
  inspectRuntimeAuthorityField,
289
314
  inspectProductSignalField,
290
315
  inspectValueFeedbackField,
316
+ inspectRequireHumanMergeField,
317
+ inspectHotfixCriteriaField,
291
318
  ];
292
319
  /** Walk registered inspectors and return one row per field (#1148). */
293
320
  export function inspectAllPolicies(projectRoot) {
@@ -306,7 +333,11 @@ export function inspectOnePolicy(name, projectRoot) {
306
333
  ? FIELD_RUNTIME_AUTHORITY
307
334
  : name === FIELD_HOST_HOOKS_CLI_ALIAS
308
335
  ? FIELD_HOST_HOOKS
309
- : name;
336
+ : name === FIELD_REQUIRE_HUMAN_MERGE_CLI_ALIAS
337
+ ? FIELD_REQUIRE_HUMAN_MERGE
338
+ : name === FIELD_HOTFIX_CRITERIA_CLI_ALIAS
339
+ ? FIELD_HOTFIX_CRITERIA
340
+ : name;
310
341
  for (const field of inspectAllPolicies(projectRoot)) {
311
342
  if (field.name === normalized)
312
343
  return field;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Slash-command intent containment (#1193 / extends #810).
3
+ *
4
+ * When a session inherits a slash command as its verb, that verb is the *only*
5
+ * authorized intent for the session. Non-implement verbs must not authorize
6
+ * implement / push / PR / merge / deploy tool paths.
7
+ *
8
+ * Provenance: set `DEFT_SESSION_SLASH_VERB` (e.g. `/github-issue`, `github-issue`,
9
+ * `/build`) for the session, or pass `sessionVerb` explicitly to pure evaluators.
10
+ */
11
+ /** Env var carrying the session's slash-command verb (#1193). */
12
+ export declare const ENV_SESSION_SLASH_VERB = "DEFT_SESSION_SLASH_VERB";
13
+ /**
14
+ * Free-text action verbs that authorize implementation under #810.
15
+ * Parallel list for slash forms lives in IMPLEMENT_SLASH_VERBS.
16
+ */
17
+ export declare const FREE_TEXT_IMPLEMENT_VERBS: readonly ["build", "implement", "ship", "swarm", "run agents", "start agent"];
18
+ /**
19
+ * Slash-command stems that authorize implementation (#1193 R1).
20
+ * Matched after normalizing `/prefix` and `:namespace` forms.
21
+ */
22
+ export declare const IMPLEMENT_SLASH_VERBS: Set<string>;
23
+ /**
24
+ * Explicit non-implement slash stems — always contained.
25
+ * Unknown stems default to non-implement (fail closed for lifecycle escalation).
26
+ */
27
+ export declare const NON_IMPLEMENT_SLASH_VERBS: Set<string>;
28
+ /** Lifecycle ops gated by the intent ceiling. */
29
+ export declare const INTENT_CEILING_OPS: readonly ["implement", "push", "pr", "merge", "deploy"];
30
+ export type IntentCeilingOp = (typeof INTENT_CEILING_OPS)[number];
31
+ export type IntentCeilingCode = "intent-allow-no-slash" | "intent-allow-implement-verb" | "intent-deny-non-implement" | "intent-deny-unknown-slash";
32
+ export interface IntentCeilingDecision {
33
+ readonly allowed: boolean;
34
+ readonly code: IntentCeilingCode;
35
+ readonly reason: string;
36
+ /** Normalized slash stem (without leading `/`), or null when no slash provenance. */
37
+ readonly sessionVerb: string | null;
38
+ readonly requestedOp: IntentCeilingOp;
39
+ }
40
+ /**
41
+ * Normalize a slash command or free-text verb to a comparable stem.
42
+ * `/deft:directive:github-issue` → `github-issue`; `/build` → `build`.
43
+ */
44
+ export declare function normalizeSessionVerb(raw: string | null | undefined): string | null;
45
+ /** True when the normalized stem authorizes implementation. */
46
+ export declare function isImplementSlashVerb(stem: string | null): boolean;
47
+ /** True when the stem is an explicitly known non-implement command. */
48
+ export declare function isNonImplementSlashVerb(stem: string | null): boolean;
49
+ export interface EvaluateIntentCeilingInput {
50
+ readonly sessionVerb?: string | null;
51
+ readonly requestedOp: IntentCeilingOp;
52
+ }
53
+ /**
54
+ * Pure intent-ceiling evaluator (#1193 R1).
55
+ *
56
+ * - No slash verb → allow (free-text #810 action-verb remains agent/AGENTS gate).
57
+ * - Implement slash verb → allow all lifecycle ops.
58
+ * - Non-implement / unknown slash verb → deny implement/push/pr/merge/deploy.
59
+ */
60
+ export declare function evaluateIntentCeiling(input: EvaluateIntentCeilingInput): IntentCeilingDecision;
61
+ /** Read session slash verb from the environment. */
62
+ export declare function readSessionSlashVerbFromEnv(env?: NodeJS.ProcessEnv): string | null;
63
+ /**
64
+ * Evaluate intent ceiling for a lifecycle op using env-backed session verb.
65
+ * Convenience for preflight / hooks / merge gates.
66
+ */
67
+ export declare function evaluateIntentCeilingFromEnv(requestedOp: IntentCeilingOp, env?: NodeJS.ProcessEnv): IntentCeilingDecision;
68
+ /**
69
+ * Map classifiable shell/MCP lifecycle signals onto intent-ceiling ops.
70
+ * Returns null when the tool path is not a gated lifecycle op.
71
+ */
72
+ export declare function intentOpFromShellSignals(signals: {
73
+ readonly isPush?: boolean;
74
+ readonly isMerge?: boolean;
75
+ readonly isPr?: boolean;
76
+ readonly isDeploy?: boolean;
77
+ readonly isImplement?: boolean;
78
+ }): IntentCeilingOp | null;
79
+ //# sourceMappingURL=intent-ceiling.d.ts.map
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Slash-command intent containment (#1193 / extends #810).
3
+ *
4
+ * When a session inherits a slash command as its verb, that verb is the *only*
5
+ * authorized intent for the session. Non-implement verbs must not authorize
6
+ * implement / push / PR / merge / deploy tool paths.
7
+ *
8
+ * Provenance: set `DEFT_SESSION_SLASH_VERB` (e.g. `/github-issue`, `github-issue`,
9
+ * `/build`) for the session, or pass `sessionVerb` explicitly to pure evaluators.
10
+ */
11
+ /** Env var carrying the session's slash-command verb (#1193). */
12
+ export const ENV_SESSION_SLASH_VERB = "DEFT_SESSION_SLASH_VERB";
13
+ /**
14
+ * Free-text action verbs that authorize implementation under #810.
15
+ * Parallel list for slash forms lives in IMPLEMENT_SLASH_VERBS.
16
+ */
17
+ export const FREE_TEXT_IMPLEMENT_VERBS = [
18
+ "build",
19
+ "implement",
20
+ "ship",
21
+ "swarm",
22
+ "run agents",
23
+ "start agent",
24
+ ];
25
+ /**
26
+ * Slash-command stems that authorize implementation (#1193 R1).
27
+ * Matched after normalizing `/prefix` and `:namespace` forms.
28
+ */
29
+ export const IMPLEMENT_SLASH_VERBS = new Set([
30
+ "build",
31
+ "implement",
32
+ "ship",
33
+ "ship-hotfix",
34
+ "swarm",
35
+ "start-agent",
36
+ "start_agent",
37
+ "run-agents",
38
+ "run_agents",
39
+ ]);
40
+ /**
41
+ * Explicit non-implement slash stems — always contained.
42
+ * Unknown stems default to non-implement (fail closed for lifecycle escalation).
43
+ */
44
+ export const NON_IMPLEMENT_SLASH_VERBS = new Set([
45
+ "github-issue",
46
+ "github_issue",
47
+ "issue",
48
+ "triage",
49
+ "refine",
50
+ "refinement",
51
+ "discuss",
52
+ "research",
53
+ "probe",
54
+ "glossary",
55
+ "sync",
56
+ "continue",
57
+ "checkpoint",
58
+ "feedback",
59
+ "article-review",
60
+ "cost",
61
+ "setup",
62
+ "interview",
63
+ "map",
64
+ "yolo",
65
+ "speckit",
66
+ "change",
67
+ ]);
68
+ /** Lifecycle ops gated by the intent ceiling. */
69
+ export const INTENT_CEILING_OPS = ["implement", "push", "pr", "merge", "deploy"];
70
+ /**
71
+ * Normalize a slash command or free-text verb to a comparable stem.
72
+ * `/deft:directive:github-issue` → `github-issue`; `/build` → `build`.
73
+ */
74
+ export function normalizeSessionVerb(raw) {
75
+ if (raw === null || raw === undefined)
76
+ return null;
77
+ let s = raw.trim().toLowerCase();
78
+ if (s.length === 0)
79
+ return null;
80
+ // Strip leading slashes.
81
+ while (s.startsWith("/"))
82
+ s = s.slice(1);
83
+ // Drop product namespaces: deft:directive:github-issue → github-issue
84
+ if (s.includes(":")) {
85
+ const parts = s.split(":").filter((p) => p.length > 0);
86
+ s = parts[parts.length - 1] ?? s;
87
+ }
88
+ // Normalize underscores to hyphens for allowlist matching.
89
+ s = s.replace(/_/g, "-");
90
+ return s.length > 0 ? s : null;
91
+ }
92
+ /** True when the normalized stem authorizes implementation. */
93
+ export function isImplementSlashVerb(stem) {
94
+ if (stem === null)
95
+ return false;
96
+ if (IMPLEMENT_SLASH_VERBS.has(stem))
97
+ return true;
98
+ // Free-text multi-word forms already hyphenated by normalize (run agents → not here).
99
+ return false;
100
+ }
101
+ /** True when the stem is an explicitly known non-implement command. */
102
+ export function isNonImplementSlashVerb(stem) {
103
+ if (stem === null)
104
+ return false;
105
+ return NON_IMPLEMENT_SLASH_VERBS.has(stem);
106
+ }
107
+ /**
108
+ * Pure intent-ceiling evaluator (#1193 R1).
109
+ *
110
+ * - No slash verb → allow (free-text #810 action-verb remains agent/AGENTS gate).
111
+ * - Implement slash verb → allow all lifecycle ops.
112
+ * - Non-implement / unknown slash verb → deny implement/push/pr/merge/deploy.
113
+ */
114
+ export function evaluateIntentCeiling(input) {
115
+ const stem = normalizeSessionVerb(input.sessionVerb ?? null);
116
+ const op = input.requestedOp;
117
+ if (stem === null) {
118
+ return {
119
+ allowed: true,
120
+ code: "intent-allow-no-slash",
121
+ reason: "No slash-command session verb; free-text #810 action-verb rules apply.",
122
+ sessionVerb: null,
123
+ requestedOp: op,
124
+ };
125
+ }
126
+ if (isImplementSlashVerb(stem)) {
127
+ return {
128
+ allowed: true,
129
+ code: "intent-allow-implement-verb",
130
+ reason: `Slash verb '/${stem}' authorizes implementation lifecycle ops.`,
131
+ sessionVerb: stem,
132
+ requestedOp: op,
133
+ };
134
+ }
135
+ const knownNon = isNonImplementSlashVerb(stem);
136
+ const code = knownNon
137
+ ? "intent-deny-non-implement"
138
+ : "intent-deny-unknown-slash";
139
+ return {
140
+ allowed: false,
141
+ code,
142
+ reason: `Slash-command intent ceiling (#1193): session verb '/${stem}' does not authorize ` +
143
+ `${op}. Non-implement verbs terminate at their named outcome; file a follow-up issue ` +
144
+ `instead of escalating to implement/push/PR/merge/deploy. ` +
145
+ `Implement verbs: /build, /ship, /ship-hotfix, /swarm, /implement.`,
146
+ sessionVerb: stem,
147
+ requestedOp: op,
148
+ };
149
+ }
150
+ /** Read session slash verb from the environment. */
151
+ export function readSessionSlashVerbFromEnv(env = process.env) {
152
+ return normalizeSessionVerb(env[ENV_SESSION_SLASH_VERB] ?? null);
153
+ }
154
+ /**
155
+ * Evaluate intent ceiling for a lifecycle op using env-backed session verb.
156
+ * Convenience for preflight / hooks / merge gates.
157
+ */
158
+ export function evaluateIntentCeilingFromEnv(requestedOp, env = process.env) {
159
+ return evaluateIntentCeiling({
160
+ sessionVerb: readSessionSlashVerbFromEnv(env),
161
+ requestedOp,
162
+ });
163
+ }
164
+ /**
165
+ * Map classifiable shell/MCP lifecycle signals onto intent-ceiling ops.
166
+ * Returns null when the tool path is not a gated lifecycle op.
167
+ */
168
+ export function intentOpFromShellSignals(signals) {
169
+ if (signals.isDeploy)
170
+ return "deploy";
171
+ if (signals.isMerge)
172
+ return "merge";
173
+ if (signals.isPr)
174
+ return "pr";
175
+ if (signals.isPush)
176
+ return "push";
177
+ if (signals.isImplement)
178
+ return "implement";
179
+ return null;
180
+ }
181
+ //# sourceMappingURL=intent-ceiling.js.map
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Official per-project opt-out flag for Directive (#2926).
3
+ *
4
+ * Presence of root `.no-deft-directive` means this project does not use
5
+ * Directive. Session ritual, doctor setup pressure, init/update install paths,
6
+ * and setup skills must honor the flag. Empty file or a short comment is enough.
7
+ *
8
+ * Product choices (v1):
9
+ * - Flag is **root-only** (workspace root the tool opened). Nested monorepo
10
+ * package roots are a documented follow-up.
11
+ * - Flag **wins locally** over ambient trusted-org / product-signal force-on.
12
+ * - Flag + deposit (`.deft/core`) is **inconsistent**: doctor **warns**;
13
+ * mutating install/update paths **fail closed**.
14
+ * - Creating the flag does **not** delete an existing deposit.
15
+ */
16
+ /** Canonical root-only filename (lowercase). Presence = flag. */
17
+ export declare const NO_DEFT_DIRECTIVE_FLAG_NAME = ".no-deft-directive";
18
+ /** One-line operator message when Directive is disabled by the flag. */
19
+ export declare const NO_DEFT_DIRECTIVE_DISABLED_MESSAGE = "Directive disabled via `.no-deft-directive`";
20
+ /**
21
+ * Loud diagnosis when both the opt-out flag and a deposit exist.
22
+ * Product choice (#2926): doctor **warns**; install/update **fail closed**.
23
+ */
24
+ export declare const NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE = "Inconsistent state: `.no-deft-directive` is present but a Directive deposit (`.deft/core`) also exists. Remove the flag to use Directive, or remove the deposit if opt-out is intentional.";
25
+ /** Recorded product choice for inconsistent flag+deposit handling. */
26
+ export declare const NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY: "warn-and-fail-closed";
27
+ export interface NoDeftDirectiveSeams {
28
+ readonly isFile?: (path: string) => boolean;
29
+ readonly isDir?: (path: string) => boolean;
30
+ }
31
+ export interface NoDeftDirectiveState {
32
+ readonly present: boolean;
33
+ readonly flagPath: string;
34
+ readonly depositPresent: boolean;
35
+ /** True when flag and deposit are both present. */
36
+ readonly inconsistent: boolean;
37
+ }
38
+ /** Absolute path to the root flag file. */
39
+ export declare function noDeftDirectiveFlagPath(projectRoot: string): string;
40
+ /**
41
+ * Detect root `.no-deft-directive` and whether a deposit is also present.
42
+ * Presence is file-existence only (empty or short comment OK).
43
+ */
44
+ export declare function detectNoDeftDirective(projectRoot: string, seams?: NoDeftDirectiveSeams): NoDeftDirectiveState;
45
+ /** True when the root opt-out flag file exists. */
46
+ export declare function isNoDeftDirectivePresent(projectRoot: string, seams?: NoDeftDirectiveSeams): boolean;
47
+ /**
48
+ * Create the root opt-out flag. Optional one-line rationale becomes a `#` comment.
49
+ * Does not remove an existing deposit.
50
+ */
51
+ export declare function createNoDeftDirectiveFlag(projectRoot: string, options?: {
52
+ rationale?: string;
53
+ }): string;
54
+ /**
55
+ * Remove the root opt-out flag when present.
56
+ * @returns true when a file was removed.
57
+ */
58
+ export declare function removeNoDeftDirectiveFlag(projectRoot: string): boolean;
59
+ //# sourceMappingURL=no-deft-directive.d.ts.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Official per-project opt-out flag for Directive (#2926).
3
+ *
4
+ * Presence of root `.no-deft-directive` means this project does not use
5
+ * Directive. Session ritual, doctor setup pressure, init/update install paths,
6
+ * and setup skills must honor the flag. Empty file or a short comment is enough.
7
+ *
8
+ * Product choices (v1):
9
+ * - Flag is **root-only** (workspace root the tool opened). Nested monorepo
10
+ * package roots are a documented follow-up.
11
+ * - Flag **wins locally** over ambient trusted-org / product-signal force-on.
12
+ * - Flag + deposit (`.deft/core`) is **inconsistent**: doctor **warns**;
13
+ * mutating install/update paths **fail closed**.
14
+ * - Creating the flag does **not** delete an existing deposit.
15
+ */
16
+ import { existsSync, statSync, unlinkSync } from "node:fs";
17
+ import { join, resolve } from "node:path";
18
+ import { containedWrite } from "../fs/contained-write.js";
19
+ import { assertWriteTargetSafe } from "../fs/projection-containment.js";
20
+ import { CANONICAL_INSTALL_ROOT } from "../init-deposit/constants.js";
21
+ /** Canonical root-only filename (lowercase). Presence = flag. */
22
+ export const NO_DEFT_DIRECTIVE_FLAG_NAME = ".no-deft-directive";
23
+ /** One-line operator message when Directive is disabled by the flag. */
24
+ export const NO_DEFT_DIRECTIVE_DISABLED_MESSAGE = "Directive disabled via `.no-deft-directive`";
25
+ /**
26
+ * Loud diagnosis when both the opt-out flag and a deposit exist.
27
+ * Product choice (#2926): doctor **warns**; install/update **fail closed**.
28
+ */
29
+ export const NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE = "Inconsistent state: `.no-deft-directive` is present but a Directive deposit (`.deft/core`) also exists. Remove the flag to use Directive, or remove the deposit if opt-out is intentional.";
30
+ /** Recorded product choice for inconsistent flag+deposit handling. */
31
+ export const NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY = "warn-and-fail-closed";
32
+ function defaultIsFile(path) {
33
+ try {
34
+ return statSync(path).isFile();
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ function defaultIsDir(path) {
41
+ try {
42
+ return statSync(path).isDirectory();
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
48
+ /** Absolute path to the root flag file. */
49
+ export function noDeftDirectiveFlagPath(projectRoot) {
50
+ return resolve(projectRoot, NO_DEFT_DIRECTIVE_FLAG_NAME);
51
+ }
52
+ /**
53
+ * Detect root `.no-deft-directive` and whether a deposit is also present.
54
+ * Presence is file-existence only (empty or short comment OK).
55
+ */
56
+ export function detectNoDeftDirective(projectRoot, seams = {}) {
57
+ const flagPath = noDeftDirectiveFlagPath(projectRoot);
58
+ const isFile = seams.isFile ?? defaultIsFile;
59
+ const isDir = seams.isDir ?? defaultIsDir;
60
+ const present = isFile(flagPath);
61
+ const depositPresent = isDir(join(projectRoot, CANONICAL_INSTALL_ROOT));
62
+ return {
63
+ present,
64
+ flagPath,
65
+ depositPresent,
66
+ inconsistent: present && depositPresent,
67
+ };
68
+ }
69
+ /** True when the root opt-out flag file exists. */
70
+ export function isNoDeftDirectivePresent(projectRoot, seams = {}) {
71
+ return detectNoDeftDirective(projectRoot, seams).present;
72
+ }
73
+ /**
74
+ * Create the root opt-out flag. Optional one-line rationale becomes a `#` comment.
75
+ * Does not remove an existing deposit.
76
+ */
77
+ export function createNoDeftDirectiveFlag(projectRoot, options = {}) {
78
+ const path = noDeftDirectiveFlagPath(projectRoot);
79
+ if (defaultIsDir(path)) {
80
+ throw new Error(`${NO_DEFT_DIRECTIVE_FLAG_NAME} exists as a directory at ${path}; remove it before creating the opt-out flag file.`);
81
+ }
82
+ const rationale = options.rationale?.trim() ?? "";
83
+ const body = rationale.length > 0 ? `# ${rationale}\n` : "";
84
+ // #2951: product write sinks route through containedWrite (create|replace).
85
+ // Use replace so re-running with a new rationale updates the flag file.
86
+ containedWrite({
87
+ root: resolve(projectRoot),
88
+ target: path,
89
+ data: body,
90
+ mode: "replace",
91
+ });
92
+ return path;
93
+ }
94
+ /**
95
+ * Remove the root opt-out flag when present.
96
+ * @returns true when a file was removed.
97
+ */
98
+ export function removeNoDeftDirectiveFlag(projectRoot) {
99
+ const path = noDeftDirectiveFlagPath(projectRoot);
100
+ if (!existsSync(path)) {
101
+ return false;
102
+ }
103
+ assertWriteTargetSafe(projectRoot, path);
104
+ if (defaultIsDir(path)) {
105
+ throw new Error(`${NO_DEFT_DIRECTIVE_FLAG_NAME} exists as a directory at ${path}; remove the directory manually before enabling Directive.`);
106
+ }
107
+ unlinkSync(path);
108
+ return true;
109
+ }
110
+ //# sourceMappingURL=no-deft-directive.js.map
@@ -9,6 +9,10 @@ export interface OrgForceOnMarker {
9
9
  readonly valueFeedback: boolean;
10
10
  readonly productSignal: boolean;
11
11
  readonly directiveVersion: string;
12
+ /** Pre-apply typed valueFeedback snapshot for verify-on-skip (#2903). */
13
+ readonly previousValueFeedback?: unknown;
14
+ /** Pre-apply typed productSignal snapshot for verify-on-skip (#2903). */
15
+ readonly previousProductSignal?: unknown;
12
16
  }
13
17
  /** Canonical typed block written by the one-time migration (#2822 Part A). */
14
18
  export declare const FORCE_ON_VALUE_FEEDBACK_BLOCK: {
@@ -17,8 +21,51 @@ export declare const FORCE_ON_VALUE_FEEDBACK_BLOCK: {
17
21
  readonly sessionLine: true;
18
22
  readonly upstreamPrompt: false;
19
23
  };
24
+ /** Normalize undefined → null and deep-sort keys so marker snapshots compare stably. */
25
+ export declare function normalizePolicySnapshot(raw: unknown): unknown;
26
+ /**
27
+ * Structural equality for policy snapshots (#2903).
28
+ * Key order independent via sortKeysDeep — discarded-PD recovery must not miss
29
+ * semantically equal blocks written with different JSON key order.
30
+ */
31
+ export declare function deepEqualPolicySnapshot(a: unknown, b: unknown): boolean;
20
32
  /** Read the #2822 migration marker when present and valid. */
21
33
  export declare function readOrgForceOnMarker(projectRoot: string): OrgForceOnMarker | null;
34
+ /**
35
+ * Sentinel previous* for an unforced field that had no stored baseline (legacy
36
+ * partial recovery). Must not deep-equal any real policy snapshot — including
37
+ * null/undefined after clear-value-feedback — or the next update would re-force.
38
+ */
39
+ export declare const ORG_FORCE_ON_NO_BASELINE: {
40
+ readonly "x-directive/org-force-on": "no-baseline";
41
+ };
42
+ /** Classic #2822 pre-migration all-false valueFeedback baseline (statusreport). */
43
+ export declare const PRE_MIGRATION_VALUE_FEEDBACK_BASELINE: {
44
+ readonly enabled: false;
45
+ readonly emitEvents: false;
46
+ readonly sessionLine: false;
47
+ readonly upstreamPrompt: false;
48
+ };
49
+ /** True when current VF looks like discarded pre-migration state (legacy markers). */
50
+ export declare function looksLikePreMigrationValueFeedback(raw: unknown): boolean;
51
+ /** True when current PS looks like discarded pre-migration state (legacy markers). */
52
+ export declare function looksLikePreMigrationProductSignal(raw: unknown): boolean;
53
+ /**
54
+ * Incomplete migration (#2903): marker present, PD still needs force-on, and
55
+ * current typed block still equals the pre-migration snapshot (or legacy marker
56
+ * has no previous* field and current still looks like the classic baseline).
57
+ *
58
+ * Exact restore of the pre-migration snapshot is treated as incomplete by design
59
+ * (company policy #2376 / #2822 / #2903): discarded working-tree force-on and
60
+ * "put the old all-false block back" are not distinguishable without git history,
61
+ * and trusted-org local collection should stay ON. True intentional opt-out must
62
+ * use a shape that differs from previous*, `policy:clear-value-feedback`, or
63
+ * root `.no-deft-directive`. Outbound product-signal still requires D17 consent.
64
+ *
65
+ * Legacy markers without previous*: only re-apply fields that still look like the
66
+ * classic pre-migration baseline so a distinct intentional sibling opt-out is kept.
67
+ */
68
+ export declare function isIncompleteForceOnField(needsForceOn: boolean, current: unknown, previousSnapshot: unknown | undefined, previousFieldPresent: boolean, field?: "valueFeedback" | "productSignal"): boolean;
22
69
  export interface RunOrgForceOnMigrationOptions {
23
70
  readonly autoEnable?: OrgAutoEnableOptions;
24
71
  readonly actor?: string;
@@ -33,6 +80,11 @@ export interface OrgForceOnMigrationResult {
33
80
  /**
34
81
  * One-time trusted-org install/upgrade force-on for valueFeedback + productSignal
35
82
  * (#2822). Idempotent via durable marker; non-trusted orgs are unchanged.
83
+ *
84
+ * #2903 verify-on-skip: when the marker is present but PROJECT-DEFINITION still
85
+ * matches the pre-migration snapshot (discarded / never-landed force-on write),
86
+ * re-apply. Intentional post-migration shapes that differ from both the previous
87
+ * snapshot and the force-on block keep the skip.
36
88
  */
37
89
  export declare function runOrgForceOnMigration(projectRoot: string, options?: RunOrgForceOnMigrationOptions): OrgForceOnMigrationResult;
38
90
  /** True when typed valueFeedback matches the #2822 force-on block shape. */