@deftai/directive-runtime 0.129.6 → 0.129.8

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 (149) hide show
  1. package/Taskfile.yml +103 -16
  2. package/commands.md +8 -3
  3. package/dist/session-start.d.ts +8 -2
  4. package/dist/session-start.js +33 -12
  5. package/dist/vbrief-preflight.d.ts +4 -0
  6. package/dist/vbrief-preflight.js +34 -0
  7. package/dist/verify-scope-provenance.d.ts +13 -1
  8. package/dist/verify-scope-provenance.js +44 -1
  9. package/docs/getting-started.md +2 -0
  10. package/docs/scope-provenance.md +15 -1
  11. package/package.json +1 -1
  12. package/packs/skills/skills-pack-0.1.json +1 -1
  13. package/scm/github.md +5 -0
  14. package/skills/deft-directive-review-cycle/SKILL.md +15 -6
  15. package/skills/deft-directive-swarm/references/host-claude-code.md +11 -1
  16. package/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  17. package/vendor/directive-content/Taskfile.yml +103 -16
  18. package/vendor/directive-content/commands.md +8 -3
  19. package/vendor/directive-content/docs/getting-started.md +2 -0
  20. package/vendor/directive-content/docs/scope-provenance.md +15 -1
  21. package/vendor/directive-content/packs/skills/skills-pack-0.1.json +1 -1
  22. package/vendor/directive-content/scm/github.md +5 -0
  23. package/vendor/directive-content/skills/deft-directive-review-cycle/SKILL.md +15 -6
  24. package/vendor/directive-content/skills/deft-directive-swarm/references/host-claude-code.md +11 -1
  25. package/vendor/directive-content/skills/deft-directive-swarm/references/host-grok-build.md +3 -2
  26. package/vendor/directive-core/dist/.tsbuildinfo +1 -1
  27. package/vendor/directive-core/dist/check/context.d.ts.map +1 -1
  28. package/vendor/directive-core/dist/check/context.js +3 -31
  29. package/vendor/directive-core/dist/check/context.js.map +1 -1
  30. package/vendor/directive-core/dist/check/gate-lists.d.ts.map +1 -1
  31. package/vendor/directive-core/dist/check/gate-lists.js +4 -32
  32. package/vendor/directive-core/dist/check/gate-lists.js.map +1 -1
  33. package/vendor/directive-core/dist/check/walk-test-files.d.ts +3 -0
  34. package/vendor/directive-core/dist/check/walk-test-files.d.ts.map +1 -0
  35. package/vendor/directive-core/dist/check/walk-test-files.js +67 -0
  36. package/vendor/directive-core/dist/check/walk-test-files.js.map +1 -0
  37. package/vendor/directive-core/dist/fs/contained-write.d.ts +8 -2
  38. package/vendor/directive-core/dist/fs/contained-write.d.ts.map +1 -1
  39. package/vendor/directive-core/dist/fs/contained-write.js +22 -6
  40. package/vendor/directive-core/dist/fs/contained-write.js.map +1 -1
  41. package/vendor/directive-core/dist/hooks/dispatcher.d.ts +7 -1
  42. package/vendor/directive-core/dist/hooks/dispatcher.d.ts.map +1 -1
  43. package/vendor/directive-core/dist/hooks/dispatcher.js +180 -10
  44. package/vendor/directive-core/dist/hooks/dispatcher.js.map +1 -1
  45. package/vendor/directive-core/dist/hooks/scope.d.ts +54 -0
  46. package/vendor/directive-core/dist/hooks/scope.d.ts.map +1 -1
  47. package/vendor/directive-core/dist/hooks/scope.js +83 -2
  48. package/vendor/directive-core/dist/hooks/scope.js.map +1 -1
  49. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts +26 -1
  50. package/vendor/directive-core/dist/init-deposit/agent-hooks.d.ts.map +1 -1
  51. package/vendor/directive-core/dist/init-deposit/agent-hooks.js +52 -3
  52. package/vendor/directive-core/dist/init-deposit/agent-hooks.js.map +1 -1
  53. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts +43 -0
  54. package/vendor/directive-core/dist/init-deposit/host-hook-outer.d.ts.map +1 -1
  55. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js +148 -0
  56. package/vendor/directive-core/dist/init-deposit/host-hook-outer.js.map +1 -1
  57. package/vendor/directive-core/dist/intake/reconcile-issues.d.ts +33 -1
  58. package/vendor/directive-core/dist/intake/reconcile-issues.d.ts.map +1 -1
  59. package/vendor/directive-core/dist/intake/reconcile-issues.js +288 -12
  60. package/vendor/directive-core/dist/intake/reconcile-issues.js.map +1 -1
  61. package/vendor/directive-core/dist/intake/thin-brief.d.ts +78 -0
  62. package/vendor/directive-core/dist/intake/thin-brief.d.ts.map +1 -0
  63. package/vendor/directive-core/dist/intake/thin-brief.js +224 -0
  64. package/vendor/directive-core/dist/intake/thin-brief.js.map +1 -0
  65. package/vendor/directive-core/dist/policy/ceremony-dial.d.ts.map +1 -1
  66. package/vendor/directive-core/dist/policy/ceremony-dial.js +2 -1
  67. package/vendor/directive-core/dist/policy/ceremony-dial.js.map +1 -1
  68. package/vendor/directive-core/dist/policy/small-change-lane.d.ts +193 -0
  69. package/vendor/directive-core/dist/policy/small-change-lane.d.ts.map +1 -0
  70. package/vendor/directive-core/dist/policy/small-change-lane.js +690 -0
  71. package/vendor/directive-core/dist/policy/small-change-lane.js.map +1 -0
  72. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts +10 -5
  73. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.d.ts.map +1 -1
  74. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js +41 -18
  75. package/vendor/directive-core/dist/pr-merge-readiness/slizard-gate.js.map +1 -1
  76. package/vendor/directive-core/dist/preflight/evaluate.d.ts +3 -0
  77. package/vendor/directive-core/dist/preflight/evaluate.d.ts.map +1 -1
  78. package/vendor/directive-core/dist/preflight/evaluate.js +15 -0
  79. package/vendor/directive-core/dist/preflight/evaluate.js.map +1 -1
  80. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts +3 -2
  81. package/vendor/directive-core/dist/preflight-gh/pre-push.d.ts.map +1 -1
  82. package/vendor/directive-core/dist/preflight-gh/pre-push.js +19 -18
  83. package/vendor/directive-core/dist/preflight-gh/pre-push.js.map +1 -1
  84. package/vendor/directive-core/dist/product-first-done-gate/evaluate.d.ts +6 -0
  85. package/vendor/directive-core/dist/product-first-done-gate/evaluate.d.ts.map +1 -1
  86. package/vendor/directive-core/dist/product-first-done-gate/evaluate.js +26 -0
  87. package/vendor/directive-core/dist/product-first-done-gate/evaluate.js.map +1 -1
  88. package/vendor/directive-core/dist/scope/decomposed-refs.d.ts +7 -0
  89. package/vendor/directive-core/dist/scope/decomposed-refs.d.ts.map +1 -1
  90. package/vendor/directive-core/dist/scope/decomposed-refs.js +27 -10
  91. package/vendor/directive-core/dist/scope/decomposed-refs.js.map +1 -1
  92. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts +218 -19
  93. package/vendor/directive-core/dist/scope/delivery-evidence.d.ts.map +1 -1
  94. package/vendor/directive-core/dist/scope/delivery-evidence.js +973 -107
  95. package/vendor/directive-core/dist/scope/delivery-evidence.js.map +1 -1
  96. package/vendor/directive-core/dist/scope/transition.d.ts +7 -1
  97. package/vendor/directive-core/dist/scope/transition.d.ts.map +1 -1
  98. package/vendor/directive-core/dist/scope/transition.js +45 -5
  99. package/vendor/directive-core/dist/scope/transition.js.map +1 -1
  100. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts +13 -3
  101. package/vendor/directive-core/dist/scope-provenance/base-fence.d.ts.map +1 -1
  102. package/vendor/directive-core/dist/scope-provenance/base-fence.js +25 -2
  103. package/vendor/directive-core/dist/scope-provenance/base-fence.js.map +1 -1
  104. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts +12 -0
  105. package/vendor/directive-core/dist/scope-provenance/evaluate.d.ts.map +1 -1
  106. package/vendor/directive-core/dist/scope-provenance/evaluate.js +41 -5
  107. package/vendor/directive-core/dist/scope-provenance/evaluate.js.map +1 -1
  108. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts +106 -0
  109. package/vendor/directive-core/dist/scope-provenance/forge-approval.d.ts.map +1 -0
  110. package/vendor/directive-core/dist/scope-provenance/forge-approval.js +382 -0
  111. package/vendor/directive-core/dist/scope-provenance/forge-approval.js.map +1 -0
  112. package/vendor/directive-core/dist/session/orientation-compression.d.ts +18 -2
  113. package/vendor/directive-core/dist/session/orientation-compression.d.ts.map +1 -1
  114. package/vendor/directive-core/dist/session/orientation-compression.js +38 -5
  115. package/vendor/directive-core/dist/session/orientation-compression.js.map +1 -1
  116. package/vendor/directive-core/dist/session/session-start.d.ts +47 -2
  117. package/vendor/directive-core/dist/session/session-start.d.ts.map +1 -1
  118. package/vendor/directive-core/dist/session/session-start.js +149 -1
  119. package/vendor/directive-core/dist/session/session-start.js.map +1 -1
  120. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts +18 -4
  121. package/vendor/directive-core/dist/swarm/approach1-babysitter.d.ts.map +1 -1
  122. package/vendor/directive-core/dist/swarm/approach1-babysitter.js +28 -4
  123. package/vendor/directive-core/dist/swarm/approach1-babysitter.js.map +1 -1
  124. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts +48 -0
  125. package/vendor/directive-core/dist/swarm/finalize-cohort.d.ts.map +1 -1
  126. package/vendor/directive-core/dist/swarm/finalize-cohort.js +175 -19
  127. package/vendor/directive-core/dist/swarm/finalize-cohort.js.map +1 -1
  128. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts +18 -3
  129. package/vendor/directive-core/dist/swarm/finalize-owed.d.ts.map +1 -1
  130. package/vendor/directive-core/dist/swarm/finalize-owed.js +139 -32
  131. package/vendor/directive-core/dist/swarm/finalize-owed.js.map +1 -1
  132. package/vendor/directive-core/dist/swarm/launch.d.ts.map +1 -1
  133. package/vendor/directive-core/dist/swarm/launch.js +4 -8
  134. package/vendor/directive-core/dist/swarm/launch.js.map +1 -1
  135. package/vendor/directive-core/dist/swarm/worker-auth-assignment.d.ts +7 -0
  136. package/vendor/directive-core/dist/swarm/worker-auth-assignment.d.ts.map +1 -1
  137. package/vendor/directive-core/dist/swarm/worker-auth-assignment.js +16 -1
  138. package/vendor/directive-core/dist/swarm/worker-auth-assignment.js.map +1 -1
  139. package/vendor/directive-core/dist/task-surface/index.d.ts.map +1 -1
  140. package/vendor/directive-core/dist/task-surface/index.js +41 -0
  141. package/vendor/directive-core/dist/task-surface/index.js.map +1 -1
  142. package/vendor/directive-core/dist/triage/evaluate/worktrees.d.ts.map +1 -1
  143. package/vendor/directive-core/dist/triage/evaluate/worktrees.js +219 -60
  144. package/vendor/directive-core/dist/triage/evaluate/worktrees.js.map +1 -1
  145. package/vendor/directive-core/dist/vbrief-reconcile/types.d.ts +5 -1
  146. package/vendor/directive-core/dist/vbrief-reconcile/types.d.ts.map +1 -1
  147. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.d.ts.map +1 -1
  148. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.js +19 -7
  149. package/vendor/directive-core/dist/vbrief-reconcile/umbrellas.js.map +1 -1
package/Taskfile.yml CHANGED
@@ -816,7 +816,9 @@ tasks:
816
816
  cmds:
817
817
  - task: engine:invoke
818
818
  vars:
819
- ENGINE_CMD: 'release-publish {{.CLI_ARGS}}'
819
+ # #5805 / #5942: the pin's default project root is its own package dir, so pass the
820
+ # operator root explicitly (an operator --project-root in CLI_ARGS still wins).
821
+ ENGINE_CMD: 'release-publish --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
820
822
 
821
823
  release:rollback:
822
824
  desc: "State-aware release unwind (#716) -- task release:rollback -- <version> [--dry-run] [--allow-low-downloads N] [--allow-data-loss] [--force-strict-0]"
@@ -825,7 +827,9 @@ tasks:
825
827
  cmds:
826
828
  - task: engine:invoke
827
829
  vars:
828
- ENGINE_CMD: 'release-rollback {{.CLI_ARGS}}'
830
+ # #5805 / #5942: the pin's default project root is its own package dir, so pass the
831
+ # operator root explicitly (an operator --project-root in CLI_ARGS still wins).
832
+ ENGINE_CMD: 'release-rollback --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
829
833
 
830
834
  release:e2e:
831
835
  desc: "End-to-end release rehearsal against an auto-created temp repo (#716, #2572 keep+report default) -- task release:e2e [-- --dry-run] [--owner OWNER] [--destroy-repo]"
@@ -834,7 +838,9 @@ tasks:
834
838
  cmds:
835
839
  - task: engine:invoke
836
840
  vars:
837
- ENGINE_CMD: 'release-e2e {{.CLI_ARGS}}'
841
+ # #5805 / #5942: the pin's default project root is its own package dir, so pass the
842
+ # operator root explicitly (an operator --project-root in CLI_ARGS still wins).
843
+ ENGINE_CMD: 'release-e2e --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
838
844
 
839
845
  release:wait-npm:
840
846
  desc: "Phase 7 wait until both public OVPR packages (@deftai/directive + @deftai/directive-runtime) list the cut version (#4267 / #5490) -- task release:wait-npm -- <version>"
@@ -945,10 +951,10 @@ tasks:
945
951
  ;;
946
952
  esac
947
953
  print_deposit_recovery() {
948
- echo "Phase 7 local closeout INCOMPLETE (#5953)." >&2
949
- echo "Recovery: merge the version-matched agent-hook deposit for $VERSION onto tip, then re-run:" >&2
954
+ echo "Phase 7 local closeout INCOMPLETE (#5953 / #6163 Prefer-A Bound 6772886751)." >&2
955
+ echo "Recovery: on the pin candidate, repair/write agent-hook deposit then force-add tip-required Cursor paths (.cursor/hooks.json + .cursor/hooks/deft-hook-<sha8>.mjs) so they co-travel with the pin (preparePinCandidateCursorDeposit / git add -f bounded set); merge that pin+deposit tip, then re-run:" >&2
950
956
  echo " task release:local-update -- $VERSION --checkout $CHECKOUT" >&2
951
- echo "Expected managed deposit paths include: .grok/hooks/ .cursor/hooks.json .claude/settings.json .codex/ (and related deposit files)." >&2
957
+ echo "Expected managed deposit paths include: .grok/hooks/ .cursor/hooks.json .cursor/hooks/deft-hook-<sha8>.mjs .claude/settings.json .codex/ (and related deposit files)." >&2
952
958
  echo "Do not auto-commit deposit bytes inside the release flow; do not npm i -g." >&2
953
959
  }
954
960
  OCC_FILE="$CHECKOUT/.deft/occupancy.json"
@@ -1015,8 +1021,16 @@ tasks:
1015
1021
  git status --short >&2
1016
1022
  exit 1
1017
1023
  fi
1018
- DEFAULT_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed "s@^refs/remotes/origin/@@" || true)"
1019
- if [ -z "$DEFAULT_BRANCH" ]; then DEFAULT_BRANCH=master; fi
1024
+ # #6164: shell parameter expansion strip (zero sed/awk/cut/grep); then git verify.
1025
+ ORIGIN_HEAD="$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)"
1026
+ DEFAULT_BRANCH="${ORIGIN_HEAD#refs/remotes/origin/}"
1027
+ if [ -z "$DEFAULT_BRANCH" ] || [ "$DEFAULT_BRANCH" = "$ORIGIN_HEAD" ]; then
1028
+ DEFAULT_BRANCH=master
1029
+ fi
1030
+ if ! git rev-parse --verify --quiet "refs/remotes/origin/$DEFAULT_BRANCH" >/dev/null; then
1031
+ echo "release:local-update: refuse; cannot verify refs/remotes/origin/$DEFAULT_BRANCH; run: git remote set-head origin -a" >&2
1032
+ exit 1
1033
+ fi
1020
1034
  CURRENT="$(git rev-parse --abbrev-ref HEAD)"
1021
1035
  if [ "$CURRENT" != "$DEFAULT_BRANCH" ]; then
1022
1036
  echo "release:local-update: refuse; checkout must be on default branch $DEFAULT_BRANCH (on $CURRENT)" >&2
@@ -1024,14 +1038,54 @@ tasks:
1024
1038
  fi
1025
1039
  echo "release:local-update: ff-only pull on $DEFAULT_BRANCH at $CHECKOUT"
1026
1040
  git pull --ff-only
1027
- if [ -f pnpm-workspace.yaml ]; then
1028
- if grep -q "minimumReleaseAgeExclude" pnpm-workspace.yaml; then
1029
- if ! grep -q "@deftai/directive-runtime@$VERSION" pnpm-workspace.yaml; then
1030
- echo "release:local-update: AgeExclude missing @deftai/directive-runtime@$VERSION before frozen install (#5953 footnote)" >&2
1031
- exit 1
1032
- fi
1033
- fi
1034
- fi
1041
+ # #6164: unconditional dependency-free AgeExclude gate (exact list-item; fail closed; no soft-skip).
1042
+ node -e '
1043
+ const fs = require("fs");
1044
+ const version = process.argv[1] || "";
1045
+ const want = "@deftai/directive-runtime@" + version;
1046
+ let text;
1047
+ try {
1048
+ text = fs.readFileSync("pnpm-workspace.yaml", "utf8");
1049
+ } catch (err) {
1050
+ const detail = err && err.message ? err.message : String(err);
1051
+ console.error("release:local-update: AgeExclude cannot read pnpm-workspace.yaml before frozen install (#6164): " + detail);
1052
+ process.exit(1);
1053
+ }
1054
+ const lines = text.split(/\r?\n/);
1055
+ const KEY_RE = /^(\s*)minimumReleaseAgeExclude:\s*(#.*)?$/;
1056
+ const ITEM_RE = /^(\s*)-\s+(.*)$/;
1057
+ const keyIdx = lines.findIndex((l) => KEY_RE.test(l));
1058
+ if (keyIdx < 0) process.exit(0);
1059
+ const keyLine = lines[keyIdx];
1060
+ const keyIndent = keyLine.length - keyLine.trimStart().length;
1061
+ const itemValue = (raw) => {
1062
+ let v = String(raw || "").trim();
1063
+ const SQ = String.fromCharCode(39);
1064
+ const DQ = String.fromCharCode(34);
1065
+ if (v.startsWith(SQ) || v.startsWith(DQ)) {
1066
+ const q = v[0];
1067
+ const end = v.indexOf(q, 1);
1068
+ return end > 0 ? v.slice(1, end) : v;
1069
+ }
1070
+ const hash = v.search(/\s#/);
1071
+ if (hash >= 0) v = v.slice(0, hash);
1072
+ return v.trim();
1073
+ };
1074
+ let found = false;
1075
+ for (let i = keyIdx + 1; i < lines.length; i++) {
1076
+ const line = lines[i];
1077
+ const trimmed = line.trim();
1078
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
1079
+ const m = ITEM_RE.exec(line);
1080
+ const indent = line.length - line.trimStart().length;
1081
+ if (m === null || indent < keyIndent) break;
1082
+ if (itemValue(m[2]) === want) { found = true; break; }
1083
+ }
1084
+ if (!found) {
1085
+ console.error("release:local-update: AgeExclude missing " + want + " before frozen install (#5953 footnote / #6164)");
1086
+ process.exit(1);
1087
+ }
1088
+ ' "$VERSION"
1035
1089
  echo "release:local-update: pnpm install --frozen-lockfile"
1036
1090
  pnpm install --frozen-lockfile
1037
1091
  PIN_PKG="$(node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); const v=(p.dependencies||{})["@deftai/directive-runtime"]||(p.devDependencies||{})["@deftai/directive-runtime"]||""; console.log(String(v).replace(/^[^0-9]*/, ""));')"
@@ -1051,6 +1105,39 @@ tasks:
1051
1105
  print_deposit_recovery
1052
1106
  exit 1
1053
1107
  fi
1108
+ # #6163 Prefer-A Bound 6772886751: committed-byte HEAD association (no repair/staging).
1109
+ echo "release:local-update: require committed Cursor deposit association on HEAD"
1110
+ if ! node --input-type=module -e '
1111
+ import { pathToFileURL } from "node:url";
1112
+ import { join, resolve } from "node:path";
1113
+ const checkout = process.argv[1];
1114
+ const vendor = join(
1115
+ checkout,
1116
+ "node_modules/@deftai/directive-runtime/vendor/directive-core/dist/init-deposit/agent-hooks.js",
1117
+ );
1118
+ let validate;
1119
+ try {
1120
+ ({ validateCursorDepositAssociation: validate } = await import(pathToFileURL(resolve(vendor)).href));
1121
+ } catch (err) {
1122
+ const detail = err && err.message ? err.message : String(err);
1123
+ console.error("release:local-update: cannot load validateCursorDepositAssociation from installed runtime (#6163): " + detail);
1124
+ process.exit(1);
1125
+ }
1126
+ if (typeof validate !== "function") {
1127
+ console.error("release:local-update: installed runtime lacks validateCursorDepositAssociation (#6163); pin must co-travel the Bound helper");
1128
+ process.exit(1);
1129
+ }
1130
+ const result = validate(checkout, { source: "HEAD" });
1131
+ if (!result || result.ok !== true) {
1132
+ console.error("release:local-update: committed Cursor deposit association FAILED on HEAD (#6163)");
1133
+ console.error(result && result.detail ? result.detail : "unknown association failure");
1134
+ process.exit(1);
1135
+ }
1136
+ console.log("release:local-update: committed Cursor deposit association ok (" + result.sha8 + ")");
1137
+ ' "$CHECKOUT"; then
1138
+ print_deposit_recovery
1139
+ exit 1
1140
+ fi
1054
1141
  UPGRADE_BIN=""
1055
1142
  if [ -x "node_modules/.bin/directive" ]; then
1056
1143
  UPGRADE_BIN="node_modules/.bin/directive"
package/commands.md CHANGED
@@ -149,6 +149,8 @@ Common commands:
149
149
  - Bare `evidence` / `disposition` keys are **not** valid (#1620 / Option B #3305). They fail `verify:vbrief-conformance` and are treated as missing by `scope:complete` (no dual-read). Migrate bare keys or narrative-only `Result`/`Verification` workarounds to the namespaced fields above while items are still non-terminal.
150
150
  - Already-terminal items (`completed`/`failed`/`cancelled`/…) are not re-checked for typed evidence; pre-marking items complete to skip the gate is unsupported for the typed path.
151
151
  - `merge`/`review` alone cannot satisfy smoke/UAT/deploy/observed_behavior criteria (title/Acceptance text or explicit axis).
152
+ - **Delivery identity (#5710):** with an issue origin, the PR must be associated with the issue as of `merged_at` by one of: (a) a unique sole `deft-story: N` mark in the merge-time PR body (PRs that change only lifecycle briefs, `CHANGELOG.md` and the spec-drift ledger are excluded and never count), (b) a closing keyword (`Closes #N`, `owner/repo#N`, or the issue URL) in the merge-time body, or (c) a Connected issue link at `merged_at`. Brief-in-PR-diff still counts. A mark, keyword or link added after merge does not count, and current `closingIssuesReferences` alone is not enough. Basis (a) scans every merged PR within 20 minutes, `graphql.remaining` ≥ 200 and REST core ≥ 100; any gap refuses. With no issue origin, stamp `plan.metadata.productPullRequest` and complete with `--pr <that number> --merge-commit <sha>`; the repository is the `origin` remote. `--non-delivery` refuses when the stamped PR is merged, and refuses when its merge state cannot be read. `xbrief:preflight` prints one note for a code-bearing brief with no issue origin.
153
+ - **Lifecycle completion (#680):** completion admission uses the merge-time bases above; there is no separate keyword-only refuse. `swarm:finalize-owed` also lists an active issue-less brief with a stamped `productPullRequest` (shown as `pr #P`, no issue number, no origin-issue check or origin-close); briefs that share a product PR finalize together under one claim. In a batched `swarm:finalize-cohort`, a refused `deft-story` mark is not an error: that story stays pending, unstamped and unswept (`refused_marks`, exit 3 incomplete), and the other stories stamp, sweep, commit and land. A failed changed-file lookup and real delivery errors still fail the batch. `scope:complete` writes the spec-drift ledger as verb output; finalize commits it with the completed briefs, and scope-provenance membership admits exactly `xbrief/.audit/spec-drift-ledger.json` only when the whole change set is a completion move. `verify:changelog-unreleased` needs no new `[Unreleased]` line when the change set versus merge-base (`git diff --name-only --no-renames`) is non-empty and only lifecycle briefs, `CHANGELOG.md` and that ledger, so lifecycle verbs write no CHANGELOG line. Product changes and an empty change set still need one.
152
154
  - `task scope:fail -- xbrief/active/<file>.xbrief.json` -- mark running work failed when the scope cannot complete.
153
155
  - `task scope:cancel -- <path>` -- move a scope to `cancelled/`.
154
156
  - `task scope:restore`, `task scope:block`, `task scope:unblock`, `task scope:demote`, and `task scope:undo:*` -- repair or reverse lifecycle transitions.
@@ -181,6 +183,8 @@ The implementation gate succeeds only for active scope xBRIEFs with `plan.status
181
183
 
182
184
  **Hotfix classifier (#1193):** Typed `plan.policy.hotfixCriteria` + pure `evaluateHotfixEligibility`. Small fix / pure revert may propose label `hotfix-candidate` only; a human promotes to `hotfix`. Refactors, new exports/handlers, and forbidden paths (Dockerfile, fly.toml, workflows, migrations, auth/secrets) never qualify.
183
185
 
186
+ **Small-change lane (#6204):** Under the `rapid` ceremony dial, a scope with stated acceptance commands may use a thin brief: title, issue ref, `file_scope` and acceptance commands only (`emitThinBrief`, `packages/core/src/intake/thin-brief.ts`). The bound defaults to 5 production files and 500 changed lines (added plus deleted, tests included; briefs and CHANGELOG excluded). `plan.policy.ceremonyDial.smallChangeLane` (`maxProductionFiles`, `maxChangedLines`) overrides each field; an invalid value refuses. `scope:promote`, `scope:activate`, `xbrief:preflight`, `verify:ac` and `scope:complete` admit the thin shape, and each one remeasures the cumulative product diff (git, against the lane-admit base in `plan.metadata["x-directive/smallChangeLane"]`; at `scope:complete`, the delivered merge commit against its first parent). A crossed bound, a public-surface change (manifest, entry, task file or export line), an unknown public surface, or a depth other than `rapid` refuses. A crossed bound or public-surface change is recorded on the brief (lane state `crossed`), so every later retry refuses even if the diff shrinks. The public-surface check reads export statements with the TypeScript parser, so a change inside a multi-line export list or exported signature counts. The one remediation is lane exit: convert the scope to the full shape (`escalateThinBriefToFullShape`), which keeps scope identity and acceptance evidence. Raising the dial (#3319) does not clear the refusal. One-PR delivery is not part of this lane; Path B brief-first stays lawful (#6203 / #680).
187
+
184
188
  ```mermaid
185
189
  flowchart TD
186
190
  Candidate["Scope xBRIEF exists"] --> Promote{"In proposed?"}
@@ -494,13 +498,14 @@ Cross-link: spawn three postures and deny recoveries live under § Agent-host di
494
498
  ### Mutable ritual (mutation posture)
495
499
 
496
500
  - ! On **mutation** session start, run `deft session:start` (or `task session:start` in framework source) after loading AGENTS.md. Records quick-tier ritual in `.deft/ritual-state.json`: alignment confirmation, branch-policy disclosure, `deft verify:tools` guidance, default-branch sync warnings, and `deft triage:welcome` one-liner. State is worktree- and HEAD-bound; stale after `plan.policy.sessionRitualStalenessHours` hours (default 8). Mutation start also claims the worktree occupancy lease (`.deft/occupancy.json`); see Session routing (#3433).
497
- - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Opt-in compact output: `deft session:start --compact` or `DEFT_SESSION_COMPACT=1` (verbose remains the default). #2176 read-only default is unchanged. Dual-path Later (orient) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
501
+ - ! **Quiet-default success stdout (#4966):** mutation `session:start` success human output defaults to one trusted go-signal line with stable prefix `[deft session] ready=…` (ready + branch/HEAD + occupancy). Routine dial/host-surface/effort/orientation/ceremony prose is verbose-only via `deft session:start --verbose` or `DEFT_SESSION_VERBOSE=1`. Non-exhaustive always-on allowlist keeps actionable zero-exit exceptions, non-default policy/bypass and human-merge disclosures, finalize-owed unknown/deferred qualifications, and actionable lifecycle/work-claim notices on the quiet path. Fail/deny/steal/non-zero stay loud. Tip `#3286` `--compact` / `DEFT_SESSION_COMPACT` are deprecated as whole-stdout volume controls (no-op for human stdout selection; `--json` / run-summary unchanged). Exit 0 is not the mutation go-signal — render from structured ready/posture/occupancy state.
502
+ - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Section formatting may still honor `--compact` / `DEFT_SESSION_COMPACT=1` inside the orientation bundle; whole-stdout volume is `#4966` quiet-default / `--verbose`. #2176 read-only default is unchanged. Dual-path Later (orient) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
498
503
  - ! Cold `session:start` does **not** run the live agent-hook probe. Functional readiness belongs to the gated mutation path so cold ceremony retains the #2990/#2991 latency profile.
499
504
  - ! **Hot path latency (#2991):** by default, mutation `session:start` does **not** block ritual-state write on optional network. It skips the npm release-availability probe and triage cache empty-hydrate / self-heal (`ensureTriageCacheHydrated` / `maybeSelfHealCache`). Targets (operator-facing, not CI-hard): warm hot path typically under a few seconds once tools are on PATH; cold path dominated by local `verify:tools` and git, usually well under ~30s when optional network is off. Empty-cache GitHub fetch-all and npm `view` previously accounted for multi-minute hangs in the WWYSYDH pilot — those stay off the critical path unless opted in.
500
505
  - ! **Cold vs re-arm ceremony tiers (#2992):** default `session:start` is the **cold** (full) path. After age staleness or compact re-arm (#2113) on the **same worktree** with continuous HEAD and previously-passing quick steps, prefer `deft session:start --rearm` (alias `--tier=rearm`) to refresh the ritual clock + HEAD/worktree bind without `verify:tools`, triage welcome, release probe, or staleness tickler. Full cold remains required for missing/invalid state, worktree change, discontinuous HEAD, first install, or failed/missing quick steps. Compact marks `rearm_needed`; PreToolUse denial and inspect/verify messages prefer re-arm recovery when cold is unnecessary.
501
506
  - ? Opt into optional network: `deft session:start --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
502
507
  - ~ `session:start --json` includes `steps[]` with `name` + `duration_ms` for major phases (`alignment`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`), plus total `duration_ms`, `optional_network`, and `ceremony_tier` (`cold` | `rearm`). Skipped optional steps report `skipped: true` and `duration_ms: 0`. Use this for attribution when investigating ceremony wall-clock.
503
- - ~ **Process-cost events (#2994 / #3508):** on mutation `session:start` completion (cold or re-arm), Directive appends a local `session:start` behavioral event to `.deft-cache/events.jsonl` with `ceremony_tier`, `duration_ms`, `exit_code`, and optional `steps[]` (same labels as `--json`). Mutation `session:start` also prints one operator-visible `ceremony <tier> <ms>` line (hidden under `--compact` / `DEFT_SESSION_COMPACT`). When PreToolUse denies for `ritual-not-ready`, a local `session:ritual-blocked` event records `tool_name`, `code`, and optional `recovery_tier` / `detail`. Always-on best-effort (never blocks ceremony or deny path); not gated on `valueFeedback`; **no remote upload** (Product Insights #2603 is a separate opt-in). Pull the rollup with `task value:show` (composed reader; CLI process time, not agent-turn wall clock). See § Process-cost events below. ⊗ Do not use the printed CLI duration as #3286 Later graduation input.
508
+ - ~ **Process-cost events (#2994 / #3508):** on mutation `session:start` completion (cold or re-arm), Directive appends a local `session:start` behavioral event to `.deft-cache/events.jsonl` with `ceremony_tier`, `duration_ms`, `exit_code`, and optional `steps[]` (same labels as `--json`). Mutation `session:start` also emits one operator-visible `ceremony <tier> <ms>` line on the verbose human path (`--verbose` / `DEFT_SESSION_VERBOSE`; quiet-default hides it per #4966). When PreToolUse denies for `ritual-not-ready`, a local `session:ritual-blocked` event records `tool_name`, `code`, and optional `recovery_tier` / `detail`. Always-on best-effort (never blocks ceremony or deny path); not gated on `valueFeedback`; **no remote upload** (Product Insights #2603 is a separate opt-in). Pull the rollup with `task value:show` (composed reader; CLI process time, not agent-turn wall clock). See § Process-cost events below. ⊗ Do not use the printed CLI duration as #3286 Later graduation input.
504
509
  - ~ At safe idle points (clean tree, no in-flight story), mutation session start and `deft scope:complete` may also run the staleness tickler: an interactive, consent-based offer to upgrade Directive (`npm i -g @deftai/directive@latest`) and/or migrate xBRIEF (`deft migrate:xbrief`). Escalation tiers, snooze windows, and opt-out live under `plan.policy.stalenessTickler` — inspect with `deft policy:show --field=stalenessTickler`. State persists in `xbrief/.triage-cache/staleness-tickler-state.json`. Skips framework source checkouts, dirty trees, CI/headless (`DEFT_SESSION_RITUAL_SKIP=1`), and typed opt-out. Refs #2488 / #2489.
505
510
  - ! Before any code-writing tool call or `start_agent` implementation dispatch, run `deft verify:session-ritual -- --tier=gated`. Gated tier fails closed unless quick-tier state is fresh; lazily records the non-deferrable `agent_hooks` readiness gate plus `deft doctor` and `task verify:cache-fresh` entrypoints. Agent-hook correctness is independent of doctor warnings and throttling. Step 0 of the pre-`start_agent` gate stack.
506
511
  - ! **One-shot recovery (#2993 / #3100 / #4399):** when PreToolUse denies writes for a stale/missing gated ritual, run `deft session:ready` (or `task session:ready`). It composes `session:start` (only when quick-tier is not green) + `verify:session-ritual -- --tier=gated` with check-class `cache_fresh` (age + live drift, no `--skip-drift-probe`) + `cache fetch-all --force` when `cache_fresh` is the remaining blocker, then re-verifies. Skip-drift ritual argv is not the ready bar — that is the `task check` / FRAMEWORK_CHECK_GATES evaluate, so stale-by-drift can still fire fetch-all. Even when gated inspect is already fresh, the fast path forces one live `agent_hooks` check and the same check-class `cache_fresh`; it still avoids unnecessary fetch-all when that evaluate is green. Flags: `--json`, `--repo OWNER/NAME`, `--with-network` (forwarded to session:start). Prefer this over juggling the multi-step recovery sequence under hook pressure.
@@ -550,7 +555,7 @@ Local ceremony cost signal for WWYSYDH / weekly process rollups. Emits to `.deft
550
555
  | PreToolUse ritual deny | `session:ritual-blocked` | Hook blocks write/spawn because gated ritual is not ready | `tool_name`, `code=ritual-not-ready`, `recovery_tier` (`cold`\|`rearm`) |
551
556
  | Per-step wall-clock | (field on `session:start`) | Same emit as session start | `steps[].name` + `steps[].duration_ms` (`alignment`, `scm_readiness`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`) |
552
557
 
553
- CLI mirror (no JSONL required): `deft session:start --json` already exposes the same `steps` / `duration_ms` / `ceremony_tier` fields for one-shot inspection. Human mutation output adds one `ceremony <tier> <ms>` line unless compact hides it (#3508).
558
+ CLI mirror (no JSONL required): `deft session:start --json` already exposes the same `steps` / `duration_ms` / `ceremony_tier` fields for one-shot inspection. Human mutation output adds one `ceremony <tier> <ms>` line on the verbose path (#3508 / #4966).
554
559
 
555
560
  Composed reader (no new verb): `task value:show` / `deft value:show` prints last cold vs re-arm duration, last per-step breakdown, blocked-ritual count, and recovery-tier distribution from the same ledger. This is **CLI process time**, not agent-turn wall clock (#3500). ⊗ Do not feed the printed CLI duration into #3286 Later graduation.
556
561
 
@@ -30,10 +30,16 @@ export interface ParsedSessionStartArgs {
30
30
  hardBudget?: boolean;
31
31
  };
32
32
  /**
33
- * #3286: compact orientation output (terse machine format). Verbose remains
34
- * the default; also settable via DEFT_SESSION_COMPACT=1.
33
+ * #3286 / #4966: tip `--compact` retained for argv compatibility; deprecated
34
+ * as whole-stdout volume gate (no-op for human stdout selection). Orientation
35
+ * section formatting may still honor it inside core.
35
36
  */
36
37
  compact: boolean;
38
+ /**
39
+ * #4966: restore full ceremony human stdout. Quiet success is the default.
40
+ * Also settable via DEFT_SESSION_VERBOSE=1.
41
+ */
42
+ verbose: boolean;
37
43
  /** #3433: steal the worktree occupancy lease. */
38
44
  steal: boolean;
39
45
  confirm: boolean;
@@ -2,7 +2,7 @@
2
2
  import { resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { CEREMONY_DEPTHS, normalizeCeremonyModelTier, normalizeCeremonyProjectShape, normalizeCeremonyTaskSize, selectCeremonyDepth, } from "../vendor/directive-core/dist/policy/index.js";
5
- import { COLD_CEREMONY_TIER, PRIMARY_CLAIM_EXCEPTIONS, parseDeferrals, parseSessionPostureToken, publishApplyingHostFromCanonicalSessionId, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, REQUIREMENTS_POSTURE, resolveProductionHostEffortDescriptor, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, } from "../vendor/directive-core/dist/session/index.js";
5
+ import { COLD_CEREMONY_TIER, PRIMARY_CLAIM_EXCEPTIONS, parseDeferrals, parseSessionPostureToken, publishApplyingHostFromCanonicalSessionId, READ_ONLY_POSTURE, REARM_CEREMONY_TIER, REQUIREMENTS_POSTURE, resolveProductionHostEffortDescriptor, resolveSessionVerbose, ritualStatePath, runSessionStart, SESSION_CEREMONY_TIERS, selectSessionStartHumanLines, } from "../vendor/directive-core/dist/session/index.js";
6
6
  function parseCeremonyDepth(raw) {
7
7
  const value = raw.trim().toLowerCase();
8
8
  if (CEREMONY_DEPTHS.includes(value)) {
@@ -40,6 +40,7 @@ export function parseArgs(argv) {
40
40
  ceremonyDepthOverride: null,
41
41
  effortBudgetHost: {},
42
42
  compact: false,
43
+ verbose: false,
43
44
  steal: false,
44
45
  confirm: false,
45
46
  occupant: null,
@@ -311,9 +312,14 @@ export function parseArgs(argv) {
311
312
  parsed.effortBudgetHost.hardBudget = true;
312
313
  }
313
314
  else if (arg === "--compact") {
314
- // #3286: terse machine orientation output (opt-in; verbose remains default)
315
+ // #3286 / #4966: deprecated as whole-stdout volume gate (parsed; no-op for
316
+ // human stdout selection). Prefer --verbose / DEFT_SESSION_VERBOSE.
315
317
  parsed.compact = true;
316
318
  }
319
+ else if (arg === "--verbose") {
320
+ // #4966: restore full ceremony dump (quiet success is the default).
321
+ parsed.verbose = true;
322
+ }
317
323
  else if (arg === "--steal") {
318
324
  parsed.steal = true;
319
325
  }
@@ -496,8 +502,10 @@ export function run(argv) {
496
502
  ceremonyTier: args.ceremonyTier,
497
503
  ceremonyDialInputs: args.ceremonyDialInputs,
498
504
  effortBudgetSeams: { hostDescriptor, environ: process.env },
499
- // #3286: --compact flag; DEFT_SESSION_COMPACT still resolved inside core.
505
+ // #3286 / #4966: --compact retained for orientation formatting; not volume.
500
506
  compact: args.compact ? true : undefined,
507
+ // #4966: --verbose / DEFT_SESSION_VERBOSE restores ceremony dump.
508
+ verbose: args.verbose ? true : undefined,
501
509
  steal: args.steal ? true : undefined,
502
510
  confirm: args.confirm ? true : undefined,
503
511
  occupant: args.occupant ?? undefined,
@@ -586,27 +594,40 @@ export function run(argv) {
586
594
  process.stdout.write(`${JSON.stringify(sorted)}\n`);
587
595
  return result.code;
588
596
  }
589
- const sink = result.code === 0 ? process.stdout : process.stderr;
590
- for (const line of lines) {
591
- sink.write(`${line}\n`);
592
- }
597
+ // #4966: one final human-output selection point (core lines + stray + trailer).
598
+ const trailer = [];
593
599
  if (result.code === 0) {
594
600
  const posture = result.payload.posture;
595
601
  if (posture === READ_ONLY_POSTURE || posture === REQUIREMENTS_POSTURE) {
596
- process.stdout.write(`[deft] ${String(result.payload.message)}\n`);
602
+ trailer.push(`[deft] ${String(result.payload.message)}`);
597
603
  }
598
604
  else {
599
- process.stdout.write(`[deft] session ritual recorded at ${ritualStatePath(projectRoot)} (diagnostic-only; not posture authority)\n`);
605
+ trailer.push(`[deft] session ritual recorded at ${ritualStatePath(projectRoot)} (diagnostic-only; not posture authority)`);
600
606
  }
601
607
  if (applyingHostPublish.status === "deferred") {
602
- process.stdout.write(`[deft] applying-host publish deferred: live occupant ${applyingHostPublish.liveSessionId} ` +
608
+ trailer.push(`[deft] applying-host publish deferred: live occupant ${applyingHostPublish.liveSessionId} ` +
603
609
  `still holds this worktree (attempted ${applyingHostPublish.attemptedSessionId}); ` +
604
- "not a conflict recovery — that owner must release/end or wait for lease TTL.\n");
610
+ "not a conflict recovery — that owner must release/end or wait for lease TTL.");
605
611
  }
606
612
  else if (applyingHostPublish.status === "failed") {
607
- process.stdout.write(`[deft] applying-host publish failed: ${applyingHostPublish.message}\n`);
613
+ trailer.push(`[deft] applying-host publish failed: ${applyingHostPublish.message}`);
608
614
  }
609
615
  }
616
+ const verbose = resolveSessionVerbose({
617
+ verbose: args.verbose ? true : undefined,
618
+ env: process.env,
619
+ });
620
+ const humanLines = selectSessionStartHumanLines({
621
+ code: result.code,
622
+ payload: payloadWithPublish,
623
+ lines: [...lines, ...trailer],
624
+ verbose,
625
+ env: process.env,
626
+ });
627
+ const sink = result.code === 0 ? process.stdout : process.stderr;
628
+ for (const line of humanLines) {
629
+ sink.write(`${line}\n`);
630
+ }
610
631
  return result.code;
611
632
  }
612
633
  if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ /** One remediation line for a code-bearing brief with no issue origin (#5710 P2). */
3
+ export declare const ISSUELESS_CODE_BEARING_REMEDIATION: string;
4
+ /** The P2 remediation line when the brief is code-bearing with no issue origin, else null. */
5
+ export declare function issuelessCodeBearingNote(briefPath: string): string | null;
2
6
  interface ParsedArgs {
3
7
  vbriefPath: string | null;
4
8
  emitJson: boolean;
@@ -1,9 +1,38 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
2
3
  import { dirname, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
  import { emitJson, evaluate, PREFLIGHT_USAGE_HINT } from "../vendor/directive-core/dist/preflight/index.js";
5
6
  import { scanWorkClaimForBriefPath } from "../vendor/directive-core/dist/scm/index.js";
7
+ import { isCodeBearingScope, resolvePlanGithubIssueRef } from "../vendor/directive-core/dist/scope/index.js";
6
8
  import { refuseIfWslOwnershipBlocked } from "./wsl-ownership-guard.js";
9
+ /** One remediation line for a code-bearing brief with no issue origin (#5710 P2). */
10
+ export const ISSUELESS_CODE_BEARING_REMEDIATION = "[deft preflight] note: code-bearing brief has no x-xbrief/github-issue origin (#5710). " +
11
+ "PR-only complete: stamp plan.metadata.productPullRequest, then " +
12
+ "scope:complete -- --pr <that PR> --merge-commit <sha>. " +
13
+ "Issue-first: add an x-xbrief/github-issue reference before the product PR.";
14
+ /** The P2 remediation line when the brief is code-bearing with no issue origin, else null. */
15
+ export function issuelessCodeBearingNote(briefPath) {
16
+ let plan;
17
+ try {
18
+ const raw = JSON.parse(readFileSync(briefPath, "utf8"));
19
+ plan =
20
+ raw !== null && typeof raw === "object" && !Array.isArray(raw)
21
+ ? raw.plan
22
+ : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ if (plan === null || typeof plan !== "object" || Array.isArray(plan)) {
28
+ return null;
29
+ }
30
+ const record = plan;
31
+ if (!isCodeBearingScope(record) || resolvePlanGithubIssueRef(record) !== null) {
32
+ return null;
33
+ }
34
+ return ISSUELESS_CODE_BEARING_REMEDIATION;
35
+ }
7
36
  const HELP_TEXT = `usage: xbrief:preflight [--vbrief-path PATH | PATH] [--json] [--help]
8
37
 
9
38
  Implementation-intent gate (#810): exits 0 only when the xBRIEF is in
@@ -117,6 +146,11 @@ export function run(argv, scan = scanWorkClaimForBriefPath) {
117
146
  `[deft work-claim] warning: scan failed (${message}). Warn is success; this is not a GitHub lock.`,
118
147
  ];
119
148
  }
149
+ // #5710 P2: advisory only; the exit code is unchanged.
150
+ const issuelessNote = issuelessCodeBearingNote(vbriefPath);
151
+ if (issuelessNote !== null) {
152
+ scanLines = [issuelessNote, ...scanLines];
153
+ }
120
154
  if (args.emitJson) {
121
155
  const message = scanLines.length > 0 ? `${result.message}\n${scanLines.join("\n")}` : result.message;
122
156
  process.stdout.write(`${emitJson(vbriefPath, result.exitCode, message)}\n`);
@@ -8,9 +8,21 @@ interface ParsedArgs {
8
8
  /** Raw `--enforce` token present, even when a later `--warn` wins `enforce`. */
9
9
  enforcePresent: boolean;
10
10
  quiet: boolean;
11
+ /** PR number for the forge-approval admit (#6203); null when absent. */
12
+ prNumber: number | null;
13
+ /** OWNER/NAME for the forge-approval admit; empty means resolve. */
14
+ repo: string;
11
15
  error?: string;
12
16
  }
13
17
  export declare function parseArgs(argv: string[]): ParsedArgs;
14
- export declare function run(argv: string[]): number;
18
+ /**
19
+ * PR context for the forge-approval admit (#6203): flags win, then
20
+ * GITHUB_PR_NUMBER (digits only) / GITHUB_REPOSITORY. Null without a PR.
21
+ */
22
+ export declare function resolveForgeContext(args: Pick<ParsedArgs, "prNumber" | "repo">, env: NodeJS.ProcessEnv): {
23
+ prNumber: number;
24
+ repo: string | null;
25
+ } | null;
26
+ export declare function run(argv: string[], env?: NodeJS.ProcessEnv): number;
15
27
  export {};
16
28
  //# sourceMappingURL=verify-scope-provenance.d.ts.map
@@ -6,12 +6,21 @@
6
6
  * evaluated inside evaluateScopeProvenance. This CLI forwards --base-ref.
7
7
  * `--enforce` / `--warn` stay parsed for compatibility; `--enforce` is a
8
8
  * no-op after #4956 / #5002 and prints a stderr notice when present.
9
+ *
10
+ * `--pr N` / `--repo OWNER/NAME` (env fallback GITHUB_PR_NUMBER /
11
+ * GITHUB_REPOSITORY) supply PR context for the forge-approved head-brief
12
+ * membership admit (#6203). Without a PR number the admit is never consulted.
9
13
  */
10
14
  import { resolve } from "node:path";
11
15
  import { scopeProvenance } from "../vendor/directive-core/dist/index.js";
12
16
  import { isDirectEntrypoint } from "./entrypoint.js";
13
17
  /** Stderr line when argv includes `--enforce`. Exit code is unchanged. */
14
18
  export const ENFORCE_NOOP_NOTICE = "verify_scope_provenance: --enforce has no effect after #4956 / #5002";
19
+ const PR_DIGITS = /^[1-9][0-9]*$/;
20
+ function parsePr(value) {
21
+ const t = value.trim();
22
+ return PR_DIGITS.test(t) ? Number(t) : null;
23
+ }
15
24
  export function parseArgs(argv) {
16
25
  const parsed = {
17
26
  projectRoot: ".",
@@ -20,6 +29,8 @@ export function parseArgs(argv) {
20
29
  enforce: false,
21
30
  enforcePresent: false,
22
31
  quiet: false,
32
+ prNumber: null,
33
+ repo: "",
23
34
  };
24
35
  for (let i = 0; i < argv.length; i += 1) {
25
36
  const arg = argv[i];
@@ -55,22 +66,54 @@ export function parseArgs(argv) {
55
66
  else if (arg?.startsWith("--base-ref=")) {
56
67
  parsed.baseRef = arg.slice("--base-ref=".length);
57
68
  }
69
+ else if (arg === "--pr" || arg?.startsWith("--pr=")) {
70
+ const value = arg === "--pr" ? argv[i + 1] : arg.slice("--pr=".length);
71
+ if (arg === "--pr")
72
+ i += 1;
73
+ const pr = value === undefined ? null : parsePr(value);
74
+ if (pr === null) {
75
+ return { ...parsed, error: "argument --pr: expected a positive PR number" };
76
+ }
77
+ parsed.prNumber = pr;
78
+ }
79
+ else if (arg === "--repo" || arg?.startsWith("--repo=")) {
80
+ const value = arg === "--repo" ? argv[i + 1] : arg.slice("--repo=".length);
81
+ if (arg === "--repo")
82
+ i += 1;
83
+ if (value === undefined || value.trim().length === 0) {
84
+ return { ...parsed, error: "argument --repo: expected OWNER/NAME" };
85
+ }
86
+ parsed.repo = value.trim();
87
+ }
58
88
  else {
59
89
  return { ...parsed, error: `unrecognized argument: ${arg}` };
60
90
  }
61
91
  }
62
92
  return parsed;
63
93
  }
64
- export function run(argv) {
94
+ /**
95
+ * PR context for the forge-approval admit (#6203): flags win, then
96
+ * GITHUB_PR_NUMBER (digits only) / GITHUB_REPOSITORY. Null without a PR.
97
+ */
98
+ export function resolveForgeContext(args, env) {
99
+ const prNumber = args.prNumber ?? parsePr(env.GITHUB_PR_NUMBER ?? "");
100
+ if (prNumber === null)
101
+ return null;
102
+ const repo = args.repo.length > 0 ? args.repo : (env.GITHUB_REPOSITORY ?? "").trim();
103
+ return { prNumber, repo: repo.length > 0 ? repo : null };
104
+ }
105
+ export function run(argv, env = process.env) {
65
106
  const args = parseArgs(argv);
66
107
  if (args.error !== undefined) {
67
108
  process.stderr.write(`verify_scope_provenance: ${args.error}\n`);
68
109
  return 2;
69
110
  }
70
111
  const projectRoot = resolve(args.projectRoot);
112
+ const forgeApproval = resolveForgeContext(args, env);
71
113
  const result = scopeProvenance.evaluateScopeProvenance(projectRoot, {
72
114
  ...(args.baseRef.length > 0 ? { baseRef: args.baseRef } : {}),
73
115
  enforce: args.enforce,
116
+ ...(forgeApproval !== null ? { forgeApproval } : {}),
74
117
  });
75
118
  if (args.enforcePresent) {
76
119
  process.stderr.write(`${ENFORCE_NOOP_NOTICE}\n`);
@@ -58,6 +58,8 @@ directive doctor
58
58
 
59
59
  After clone or a new worktree: run the project package manager (`npm install` / `pnpm install`), then the first eligible runtime command or registered hook completes local activation.
60
60
 
61
+ **Dependabot / pin-only bumps (AC14.10):** a PR that changes only the `@deftai/directive-runtime` pin + lockfile is valid. After `pnpm install` / `npm install` brings `node_modules` to the new pin, the first eligible skill, hook, or ordinary runtime command runs local automatic setup (wiring / `GENERATION` / readiness). `directive doctor` stays read-only — dirty doctor output before that eligible invocation is expected. CI may either rely on that first eligible command or run explicit `directive install --repair --yes`. Do not treat "deposit bytes must land in the same pin PR" as product law. Human-test mirror: HT-17 in `docs/installer-refactor-ovpr/install-ovpr-human-test-plan.md`. Schema/data behind the new pin still needs `directive upgrade` (AC14.11), not repair alone.
62
+
61
63
  **Repair (pin unchanged):** `npm i -g @deftai/directive`, then `directive install --repair`.
62
64
 
63
65
  **Upgrade version or migrate data:** `directive upgrade` (alias: `update`).
@@ -45,10 +45,24 @@ Allowance = clamp(count of **concrete** production files on the merge-base `file
45
45
  When the bound story is in the change set:
46
46
 
47
47
  1. A human-stamped merge-base mint for the **continuity-resolved** story is the allowlist when present (planId match ignores stale `xbriefRelPath`; no-`plan.id` is path-first + basename-keyed mint with matching `xbriefRelPath`).
48
- 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen.
48
+ 2. Otherwise membership evaluates against the merge-base brief's **concrete** `file_scope` as agent precommitment. Head brief cannot widen, except under a qualifying forge approval (#6203, below).
49
49
  3. Free paths: bound brief, verified peer briefs, `CHANGELOG.md`, and continuity lifecycle paths of the bound identity. An xBRIEF-only change set may omit a mint.
50
50
  4. Empty / invalid / unreadable mint records fail closed (they are not absence).
51
51
 
52
+ #### Forge-approved head brief (#6203)
53
+
54
+ A PR can carry a new or widened brief plus its code when a human approves it on GitHub. With PR context (`--pr N` / `--repo OWNER/NAME`, or `GITHUB_PR_NUMBER` / `GITHUB_REPOSITORY` in CI), a failing membership check under precommitment or missing authority consults the forge. When a qualifying approval exists, membership evaluates against the **head** brief's concrete `file_scope` instead of the merge-base brief.
55
+
56
+ - **Mint wins.** A present mint keeps the mint path. The forge is never consulted.
57
+ - **Auth-hash v1** (`sha256:` over canonical JSON; strings byte-exact, no Unicode normalization) covers `plan.id`, the xbrief path, the concrete `file_scope`, and the acceptanceCriteria array: `plan.acceptance`, `plan.narratives.AcceptanceCriteria`, and each item's `narrative.Acceptance`, in document order. `productPullRequest` and other delivery metadata stay out, so a PPR stamp keeps the approval. Any change to the covered fields drops it.
58
+ - **Qualifying approval:** the reviewer's latest deciding review (APPROVED / CHANGES_REQUESTED / DISMISSED) is APPROVED; the reviewer is a `User` (not Bot or App), not the PR author, and has `write`, `maintain` or `admin` on the repo; the repo is the checkout's `origin` (a `--repo` value may only confirm it, so a fork's approval never counts) and is the PR's base repo; the evaluated HEAD is the PR head (or GitHub's merge commit whose second parent is the PR head); and the brief at the reviewed commit hashes the same as the head brief.
59
+ - **Fallback:** missing PR context, no qualifying approval, a hash mismatch, or an unavailable forge keeps today's Path B or mint finding unchanged. The result message adds one `forge approval (#6203)` line with the reason.
60
+ - **Membership only.** The production fence still reads the merge-base brief. A widened brief can pass membership and still fail `production-scope-over-budget`. A brand-new brief has no production fence (as today).
61
+ - **Re-run after approval.** Approval does not re-trigger CI. Re-run the merge gate (or push) after the approval lands.
62
+ - **Residual (#6207):** an agent that holds a second human forge token can satisfy every predicate above. The hash binds the approved bytes; it does not prove independent human intent and does not close that confused-deputy risk. That posture stays open on #6207.
63
+
64
+ Code: `packages/core/src/scope-provenance/forge-approval.ts`.
65
+
52
66
  #### Path A planning split (#5551)
53
67
 
54
68
  When an active implementation brief is **outside** the change set, membership is a no-op for that identity (Path A). Supported planning publication is two change sets, not one:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-runtime",
3
- "version": "0.129.6",
3
+ "version": "0.129.8",
4
4
  "description": "Directive runtime — pinned product commands, hooks, content, and engine.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://deftai.github.io/directive/",