safeword 0.72.0 → 0.73.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 (81) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks.json +5 -5
  3. package/codex-plugin/skills/bdd/references/SCENARIOS.md +6 -6
  4. package/codex-plugin/skills/closeout/SKILL.md +20 -7
  5. package/dist/{chunk-PGUVVBIN.js → chunk-7BLT7HXQ.js} +1 -1
  6. package/dist/chunk-7BLT7HXQ.js.map +1 -0
  7. package/dist/{chunk-7NZ26QJP.js → chunk-7D2H4IZ5.js} +3 -3
  8. package/dist/chunk-FTCNAEAL.js +117 -0
  9. package/dist/chunk-FTCNAEAL.js.map +1 -0
  10. package/dist/{chunk-KWBGQ2YZ.js → chunk-HKFPZSXT.js} +2 -2
  11. package/dist/{chunk-F4CA33GP.js → chunk-IWU7G6G7.js} +19 -15
  12. package/dist/chunk-IWU7G6G7.js.map +1 -0
  13. package/dist/chunk-KNKA4KSG.js +632 -0
  14. package/dist/chunk-KNKA4KSG.js.map +1 -0
  15. package/dist/chunk-MF7ZXVYN.js +318 -0
  16. package/dist/chunk-MF7ZXVYN.js.map +1 -0
  17. package/dist/{migrate-codex-plugin-CPLYYCGP.js → chunk-NDY73DUG.js} +430 -146
  18. package/dist/chunk-NDY73DUG.js.map +1 -0
  19. package/dist/{chunk-QPWX5JXI.js → chunk-QOGMLRPC.js} +51 -5
  20. package/dist/chunk-QOGMLRPC.js.map +1 -0
  21. package/dist/{chunk-XIGSW56Z.js → chunk-VB7VIQRO.js} +118 -20
  22. package/dist/chunk-VB7VIQRO.js.map +1 -0
  23. package/dist/{chunk-4HWB6QFG.js → chunk-WWF42RYW.js} +2 -2
  24. package/dist/{chunk-I5QEPYVK.js → chunk-ZERKNRHZ.js} +8 -122
  25. package/dist/chunk-ZERKNRHZ.js.map +1 -0
  26. package/dist/{cleanup-STJVSD7S.js → cleanup-XX2KWNNQ.js} +34 -23
  27. package/dist/cleanup-XX2KWNNQ.js.map +1 -0
  28. package/dist/cli.js +65 -23
  29. package/dist/cli.js.map +1 -1
  30. package/dist/codex-bootstrap-VOQXVQ6F.js +124 -0
  31. package/dist/codex-bootstrap-VOQXVQ6F.js.map +1 -0
  32. package/dist/{codex-hook-HBEPMPEK.js → codex-hook-DZ4HY5V5.js} +20 -13
  33. package/dist/codex-hook-DZ4HY5V5.js.map +1 -0
  34. package/dist/{converge-setup-6WS2RMSG.js → converge-setup-XVQEDGLV.js} +75 -25
  35. package/dist/converge-setup-XVQEDGLV.js.map +1 -0
  36. package/dist/{finalization-3YYUBUTJ.js → finalization-ES4OERZA.js} +3 -3
  37. package/dist/migrate-codex-plugin-YCE6PINW.js +38 -0
  38. package/dist/{plan-RRN2ESJ7.js → plan-AX35QFPS.js} +5 -5
  39. package/dist/{profile-DM4UNHMN.js → profile-SNTDKMIH.js} +10 -4
  40. package/dist/{remove-GNAT5APA.js → remove-6BPYCP7W.js} +5 -5
  41. package/dist/{retro-XO7CALV4.js → retro-4B2WJD2S.js} +109 -28
  42. package/dist/retro-4B2WJD2S.js.map +1 -0
  43. package/dist/{retro-extract-E5F5A35R.js → retro-extract-72SKMRZE.js} +10 -2
  44. package/dist/{status-5DDTQIJU.js → status-KQTI2O3O.js} +7 -5
  45. package/dist/status-KQTI2O3O.js.map +1 -0
  46. package/dist/{status-ILKQIXSD.js → status-Q4FPQNSV.js} +10 -8
  47. package/dist/{status-ILKQIXSD.js.map → status-Q4FPQNSV.js.map} +1 -1
  48. package/package.json +1 -1
  49. package/templates/SAFEWORD.md +1 -1
  50. package/templates/hooks/lib/closeout-binding.ts +29 -6
  51. package/templates/hooks/lib/quality.ts +261 -15
  52. package/templates/hooks/lib/retro-extract.ts +192 -33
  53. package/templates/hooks/record-skill-invocation.ts +3 -1
  54. package/templates/hooks/session-reply-format.ts +27 -0
  55. package/templates/hooks/stop-quality.ts +9 -1
  56. package/templates/scripts/closeout-cleanup.ts +229 -50
  57. package/templates/skills/bdd/SCENARIOS.md +6 -6
  58. package/templates/skills/closeout/SKILL.md +20 -7
  59. package/dist/chunk-2MQTOSGG.js +0 -312
  60. package/dist/chunk-2MQTOSGG.js.map +0 -1
  61. package/dist/chunk-F4CA33GP.js.map +0 -1
  62. package/dist/chunk-I5QEPYVK.js.map +0 -1
  63. package/dist/chunk-PGUVVBIN.js.map +0 -1
  64. package/dist/chunk-QPWX5JXI.js.map +0 -1
  65. package/dist/chunk-XIGSW56Z.js.map +0 -1
  66. package/dist/chunk-Y6LUZKTN.js +0 -144
  67. package/dist/chunk-Y6LUZKTN.js.map +0 -1
  68. package/dist/cleanup-STJVSD7S.js.map +0 -1
  69. package/dist/codex-hook-HBEPMPEK.js.map +0 -1
  70. package/dist/converge-setup-6WS2RMSG.js.map +0 -1
  71. package/dist/migrate-codex-plugin-CPLYYCGP.js.map +0 -1
  72. package/dist/retro-XO7CALV4.js.map +0 -1
  73. /package/dist/{chunk-7NZ26QJP.js.map → chunk-7D2H4IZ5.js.map} +0 -0
  74. /package/dist/{chunk-KWBGQ2YZ.js.map → chunk-HKFPZSXT.js.map} +0 -0
  75. /package/dist/{chunk-4HWB6QFG.js.map → chunk-WWF42RYW.js.map} +0 -0
  76. /package/dist/{finalization-3YYUBUTJ.js.map → finalization-ES4OERZA.js.map} +0 -0
  77. /package/dist/{profile-DM4UNHMN.js.map → migrate-codex-plugin-YCE6PINW.js.map} +0 -0
  78. /package/dist/{plan-RRN2ESJ7.js.map → plan-AX35QFPS.js.map} +0 -0
  79. /package/dist/{retro-extract-E5F5A35R.js.map → profile-SNTDKMIH.js.map} +0 -0
  80. /package/dist/{remove-GNAT5APA.js.map → remove-6BPYCP7W.js.map} +0 -0
  81. /package/dist/{status-5DDTQIJU.js.map → retro-extract-72SKMRZE.js.map} +0 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safeword",
3
- "version": "0.72.0",
3
+ "version": "0.73.0",
4
4
  "description": "Safe Word workflow guidance and gates for Codex.",
5
5
  "author": {
6
6
  "name": "Arcade AI"
@@ -6,7 +6,7 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "bunx --bun safeword@0.72.0 hook codex session-start --plugin-hook",
9
+ "command": "bunx --bun safeword@0.73.0 hook codex session-start --plugin-hook",
10
10
  "timeout": 120,
11
11
  "statusMessage": "Loading Safe Word standing instructions"
12
12
  }
@@ -19,7 +19,7 @@
19
19
  "hooks": [
20
20
  {
21
21
  "type": "command",
22
- "command": "bunx --bun safeword@0.72.0 hook codex pre-tool-use --plugin-hook",
22
+ "command": "bunx --bun safeword@0.73.0 hook codex pre-tool-use --plugin-hook",
23
23
  "timeout": 30,
24
24
  "statusMessage": "Checking Safe Word edit gates"
25
25
  }
@@ -32,7 +32,7 @@
32
32
  "hooks": [
33
33
  {
34
34
  "type": "command",
35
- "command": "bunx --bun safeword@0.72.0 hook codex post-tool-use --plugin-hook",
35
+ "command": "bunx --bun safeword@0.73.0 hook codex post-tool-use --plugin-hook",
36
36
  "timeout": 30,
37
37
  "statusMessage": "Surfacing Safe Word post-tool context"
38
38
  }
@@ -45,7 +45,7 @@
45
45
  "hooks": [
46
46
  {
47
47
  "type": "command",
48
- "command": "bunx --bun safeword@0.72.0 hook codex user-prompt-submit --plugin-hook",
48
+ "command": "bunx --bun safeword@0.73.0 hook codex user-prompt-submit --plugin-hook",
49
49
  "timeout": 30,
50
50
  "statusMessage": "Checking queued Safe Word prompt context"
51
51
  }
@@ -58,7 +58,7 @@
58
58
  "hooks": [
59
59
  {
60
60
  "type": "command",
61
- "command": "bunx --bun safeword@0.72.0 hook codex stop --plugin-hook",
61
+ "command": "bunx --bun safeword@0.73.0 hook codex stop --plugin-hook",
62
62
  "timeout": 600,
63
63
  "statusMessage": "Checking Safe Word stop continuation"
64
64
  }
@@ -18,9 +18,9 @@
18
18
 
19
19
  Save the dimension table to `dimensions.md` in the ticket folder before writing test-definitions.md (the pre-tool hook enforces this for features). For tiny features with one obvious behavioral dimension and no partitioning to enumerate, dimensions.md may instead be a single line `skip: <non-empty reason>`.
20
20
 
21
- ### Scenario saturation
21
+ ### Are the scenarios complete?
22
22
 
23
- Each propose-and-converge turn either surfaces new scenarios or doesn't. When a turn produces no new scenarios → proceed to scenario-gate.
23
+ Ask the user: **Do these scenarios fully describe the intended behavior and important boundaries, or is anything missing?** If their feedback adds scenarios, revise the set and ask again. When they confirm nothing is missing, proceed to scenario-gate.
24
24
 
25
25
  ### Concrete example
26
26
 
@@ -55,7 +55,7 @@ Each propose-and-converge turn either surfaces new scenarios or doesn't. When a
55
55
 
56
56
  **User:** "Looks good, proceed."
57
57
 
58
- **Result:** No new scenarios → scenario saturation → proceed to scenario-gate.
58
+ **Result:** The user confirms the intended behavior and important boundaries are fully described → proceed to scenario-gate.
59
59
 
60
60
  ### Two formats: discovery vs saved
61
61
 
@@ -198,11 +198,11 @@ delivery retries on exponential backoff`). IDs are 1-indexed per job and
198
198
 
199
199
  **Entry:** Agent enters `scenario-gate` phase.
200
200
 
201
- Run the **`$safeword:review-spec`** skill — it is the gate procedure (vacuous-pass, AODI, determinism risks, adversarial pass + negative-case, cross-cutting checks, and the findings format). It reads the active ticket's `.feature` source when present, using `test-definitions.md` only as the R/G/R ledger, reports findings, and is re-invokable standalone after scenario edits. Apply its findings, then complete the saturation check and exit below.
201
+ Run the **`$safeword:review-spec`** skill — it is the gate procedure (vacuous-pass, AODI, determinism risks, adversarial pass + negative-case, cross-cutting checks, and the findings format). It reads the active ticket's `.feature` source when present, using `test-definitions.md` only as the R/G/R ledger, reports findings, and is re-invokable standalone after scenario edits. Apply its findings, then complete the plain-language completeness check and exit below.
202
202
 
203
- ### Coverage saturation
203
+ ### Are the reviewed scenarios complete?
204
204
 
205
- If the adversarial pass + user feedback produced new scenarios → loop back to define-behavior. If nothing new surfaced → done.
205
+ Ask the user: **Do these scenarios now fully cover the intended behavior and important boundaries, or is anything still missing?** If the adversarial pass or user feedback produced new scenarios, loop back to define-behavior. When nothing is missing, the quality gate is complete.
206
206
 
207
207
  ### Scenario Gate Exit
208
208
 
@@ -27,6 +27,9 @@ Collect and report every blocker. Missing, stale, failing, pending, unknown, or
27
27
  ambiguous evidence means **no merge or cleanup**. A merge command's exit status
28
28
  never proves that the pull request is merged.
29
29
 
30
+ Dependency audit is part of this delivery-time verification boundary. Resolve
31
+ its failures before merge while the pull request head can still be changed.
32
+
30
33
  ## 2. Respect merge authority
31
34
 
32
35
  Invocation alone grants no merge authority. Read authority only from the current user request;
@@ -63,13 +66,20 @@ complete only after proving it was the exact planned target. If the pull request
63
66
  is merged, its retrospective is complete, and its exact branch and worktree are
64
67
  already absent, report that the session is already closed.
65
68
 
69
+ The guard records a private, atomic verification receipt in Git's shared common
70
+ directory only after every verification lane passes on a clean exact PR head.
71
+ For 24 hours, that receipt can prove the immutable head when an interrupted
72
+ cleanup must resume from a surviving worktree after the topic worktree is gone.
73
+ A missing, stale, malformed, dirty-state, or wrong-head receipt blocks cleanup.
74
+
66
75
  ## 4. Complete the current session's retrospective
67
76
 
68
77
  After merge is independently confirmed, invoke the cleanup guard in preview
69
78
  mode. Its host hook supplies a short-lived, single-consumer binding to this exact
70
- session (and Cursor transcript). A missing or expired binding fails closed;
71
- there is no newest-session fallback and callers cannot nominate another receipt,
72
- session, transcript, or spool.
79
+ session (and Cursor transcript). Codex Desktop may instead supply its authenticated
80
+ current `CODEX_THREAD_ID`, consistent with SafeWord's other Codex identity bridges.
81
+ A missing or expired binding or identity fails closed; there is no newest-session
82
+ fallback and callers cannot nominate another receipt, session, transcript, or spool.
73
83
 
74
84
  The guard runs `safeword retro run --json` itself and accepts only a
75
85
  successful result whose `data.agent_filing_needed` is `false` and whose derived
@@ -89,10 +99,13 @@ Run the guard from the delivery worktree; preview is the default:
89
99
  bun .safeword/scripts/closeout-cleanup.ts --pr PR_NUMBER
90
100
  ```
91
101
 
92
- The preview reruns the project's verification, build, typecheck, BDD, and
93
- dependency plans and binds the resulting repository state and exact PR identity
94
- to `PLAN_DIGEST`. Report the complete operation list and all blockers. Do not
95
- apply a blocked plan.
102
+ At the exact delivery head, the post-merge preview reruns the project's
103
+ verification, build, typecheck, and BDD plans. It does not rerun dependency audit:
104
+ that changing intelligence is enforced at the delivery-time, pre-merge boundary
105
+ and cannot repair an immutable merged head. After that worktree is gone, preview
106
+ requires its fresh clean-head receipt instead. It binds the resulting repository
107
+ state and exact PR identity to `PLAN_DIGEST`. Report the complete operation list
108
+ and all blockers. Do not apply a blocked plan.
96
109
 
97
110
  With the user's cleanup intent already established by invoking closeout, apply
98
111
  only the unchanged preview:
@@ -11,4 +11,4 @@ var CodexMigrationError = class extends Error {
11
11
  export {
12
12
  CodexMigrationError
13
13
  };
14
- //# sourceMappingURL=chunk-PGUVVBIN.js.map
14
+ //# sourceMappingURL=chunk-7BLT7HXQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/codex-plugin/migration-error.ts"],"sourcesContent":["export type CodexMigrationErrorCode =\n | 'PLUGIN_ENABLEMENT_UNKNOWN'\n | 'PLUGIN_ENABLEMENT_FAILED'\n | 'PLUGIN_MARKETPLACE_FAILED'\n | 'PLUGIN_PROFILE_BUSY'\n | 'PLUGIN_NEWER_PIN_PRESERVED'\n | 'AMBIGUOUS_LEGACY_CONFIG'\n | 'UNSAFE_MIGRATION_PATH'\n | 'BACKUP_EXISTS'\n | 'ROLLBACK_FAILED'\n | 'RECOVERY_CONFLICT'\n | 'PLAN_STALE'\n | 'FINALIZATION_PROOF_REQUIRED';\n\nexport class CodexMigrationError extends Error {\n readonly code: CodexMigrationErrorCode;\n\n constructor(code: CodexMigrationErrorCode, message: string, options?: ErrorOptions) {\n super(message, options);\n this.name = 'CodexMigrationError';\n this.code = code;\n }\n}\n"],"mappings":";AAcO,IAAM,sBAAN,cAAkC,MAAM;AAAA,EACpC;AAAA,EAET,YAAY,MAA+B,SAAiB,SAAwB;AAClF,UAAM,SAAS,OAAO;AACtB,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;","names":[]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createProjectContext,
3
3
  reconcile
4
- } from "./chunk-KWBGQ2YZ.js";
4
+ } from "./chunk-HKFPZSXT.js";
5
5
  import {
6
6
  SAFEWORD_SCHEMA
7
- } from "./chunk-F4CA33GP.js";
7
+ } from "./chunk-IWU7G6G7.js";
8
8
  import {
9
9
  createPlan
10
10
  } from "./chunk-RLH5PLSD.js";
@@ -99,4 +99,4 @@ export {
99
99
  createReconciliationPlan,
100
100
  applyReconciliation
101
101
  };
102
- //# sourceMappingURL=chunk-7NZ26QJP.js.map
102
+ //# sourceMappingURL=chunk-7D2H4IZ5.js.map
@@ -0,0 +1,117 @@
1
+ // src/utils/version.ts
2
+ function onlyAsciiDigits(value) {
3
+ if (value.length === 0) return false;
4
+ for (const character of value) {
5
+ if (character < "0" || character > "9") return false;
6
+ }
7
+ return true;
8
+ }
9
+ function validSemverIdentifier(value) {
10
+ if (value.length === 0) return false;
11
+ for (const character of value) {
12
+ const valid = character >= "0" && character <= "9" || character >= "A" && character <= "Z" || character >= "a" && character <= "z" || character === "-";
13
+ if (!valid) return false;
14
+ }
15
+ return true;
16
+ }
17
+ function validSemverIdentifiers(value) {
18
+ return value.split(".").every((identifier) => validSemverIdentifier(identifier));
19
+ }
20
+ function hasForbiddenLeadingZero(value) {
21
+ return value.length > 1 && value.startsWith("0");
22
+ }
23
+ function parseCore(value) {
24
+ const parts = value.split(".");
25
+ if (parts.length !== 3 || parts.some((part) => !onlyAsciiDigits(part) || hasForbiddenLeadingZero(part))) {
26
+ return void 0;
27
+ }
28
+ return parts;
29
+ }
30
+ function parseIdentifiers(value, forbidNumericLeadingZeros) {
31
+ if (!validSemverIdentifiers(value)) return void 0;
32
+ const identifiers = value.split(".");
33
+ if (forbidNumericLeadingZeros && identifiers.some(
34
+ (identifier) => onlyAsciiDigits(identifier) && hasForbiddenLeadingZero(identifier)
35
+ )) {
36
+ return void 0;
37
+ }
38
+ return identifiers;
39
+ }
40
+ function parseSemver(version) {
41
+ const buildParts = version.split("+");
42
+ if (buildParts.length > 2) return void 0;
43
+ const withoutBuild = buildParts[0] ?? "";
44
+ const build = buildParts[1];
45
+ if (build !== void 0 && parseIdentifiers(build, false) === void 0) return void 0;
46
+ const prereleaseSeparator = withoutBuild.indexOf("-");
47
+ const coreText = prereleaseSeparator === -1 ? withoutBuild : withoutBuild.slice(0, prereleaseSeparator);
48
+ const prereleaseText = prereleaseSeparator === -1 ? void 0 : withoutBuild.slice(prereleaseSeparator + 1);
49
+ const core = parseCore(coreText);
50
+ if (core === void 0) return void 0;
51
+ const prerelease = prereleaseText === void 0 ? [] : parseIdentifiers(prereleaseText, true);
52
+ return prerelease === void 0 ? void 0 : { core, prerelease };
53
+ }
54
+ function compareNumeric(left, right) {
55
+ const leftValue = BigInt(left);
56
+ const rightValue = BigInt(right);
57
+ if (leftValue < rightValue) return -1;
58
+ if (leftValue > rightValue) return 1;
59
+ return 0;
60
+ }
61
+ function comparePrereleaseIdentifier(left, right) {
62
+ if (left === right) return 0;
63
+ const leftNumeric = onlyAsciiDigits(left);
64
+ const rightNumeric = onlyAsciiDigits(right);
65
+ if (leftNumeric && rightNumeric) return compareNumeric(left, right);
66
+ if (leftNumeric) return -1;
67
+ if (rightNumeric) return 1;
68
+ return left < right ? -1 : 1;
69
+ }
70
+ function comparePrerelease(left, right) {
71
+ if (left.length === 0 && right.length === 0) return 0;
72
+ if (left.length === 0) return 1;
73
+ if (right.length === 0) return -1;
74
+ for (let index = 0; index < Math.max(left.length, right.length); index++) {
75
+ const leftIdentifier = left[index];
76
+ if (leftIdentifier === void 0) return -1;
77
+ const rightIdentifier = right[index];
78
+ if (rightIdentifier === void 0) return 1;
79
+ const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);
80
+ if (comparison !== 0) return comparison;
81
+ }
82
+ return 0;
83
+ }
84
+ function compareCore(left, right) {
85
+ for (let index = 0; index < 3; index++) {
86
+ const comparison = compareNumeric(left[index] ?? "0", right[index] ?? "0");
87
+ if (comparison !== 0) return comparison;
88
+ }
89
+ return 0;
90
+ }
91
+ function compareLegacyVersions(left, right) {
92
+ const leftParts = left.split(".").map(Number);
93
+ const rightParts = right.split(".").map(Number);
94
+ for (let index = 0; index < 3; index++) {
95
+ const leftValue = leftParts[index] ?? 0;
96
+ const rightValue = rightParts[index] ?? 0;
97
+ if (leftValue < rightValue) return -1;
98
+ if (leftValue > rightValue) return 1;
99
+ }
100
+ return 0;
101
+ }
102
+ function compareVersions(a, b) {
103
+ const parsedA = parseSemver(a);
104
+ const parsedB = parseSemver(b);
105
+ if (parsedA === void 0 || parsedB === void 0) return compareLegacyVersions(a, b);
106
+ const coreComparison = compareCore(parsedA.core, parsedB.core);
107
+ return coreComparison === 0 ? comparePrerelease(parsedA.prerelease, parsedB.prerelease) : coreComparison;
108
+ }
109
+ function isSafePackageVersion(version) {
110
+ return parseSemver(version) !== void 0;
111
+ }
112
+
113
+ export {
114
+ compareVersions,
115
+ isSafePackageVersion
116
+ };
117
+ //# sourceMappingURL=chunk-FTCNAEAL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/version.ts"],"sourcesContent":["/**\n * Version comparison utilities\n */\n\nfunction onlyAsciiDigits(value: string): boolean {\n if (value.length === 0) return false;\n for (const character of value) {\n if (character < '0' || character > '9') return false;\n }\n return true;\n}\n\nfunction validSemverIdentifier(value: string): boolean {\n if (value.length === 0) return false;\n for (const character of value) {\n const valid =\n (character >= '0' && character <= '9') ||\n (character >= 'A' && character <= 'Z') ||\n (character >= 'a' && character <= 'z') ||\n character === '-';\n if (!valid) return false;\n }\n return true;\n}\n\nfunction validSemverIdentifiers(value: string): boolean {\n return value.split('.').every(identifier => validSemverIdentifier(identifier));\n}\n\ninterface ParsedSemver {\n readonly core: readonly [string, string, string];\n readonly prerelease: readonly string[];\n}\n\ntype Comparison = -1 | 0 | 1;\n\nfunction hasForbiddenLeadingZero(value: string): boolean {\n return value.length > 1 && value.startsWith('0');\n}\n\nfunction parseCore(value: string): ParsedSemver['core'] | undefined {\n const parts = value.split('.');\n if (\n parts.length !== 3 ||\n parts.some(part => !onlyAsciiDigits(part) || hasForbiddenLeadingZero(part))\n ) {\n return undefined;\n }\n return parts as [string, string, string];\n}\n\nfunction parseIdentifiers(\n value: string,\n forbidNumericLeadingZeros: boolean,\n): readonly string[] | undefined {\n if (!validSemverIdentifiers(value)) return undefined;\n const identifiers = value.split('.');\n if (\n forbidNumericLeadingZeros &&\n identifiers.some(\n identifier => onlyAsciiDigits(identifier) && hasForbiddenLeadingZero(identifier),\n )\n ) {\n return undefined;\n }\n return identifiers;\n}\n\nfunction parseSemver(version: string): ParsedSemver | undefined {\n const buildParts = version.split('+');\n if (buildParts.length > 2) return undefined;\n const withoutBuild = buildParts[0] ?? '';\n const build = buildParts[1];\n if (build !== undefined && parseIdentifiers(build, false) === undefined) return undefined;\n\n const prereleaseSeparator = withoutBuild.indexOf('-');\n const coreText =\n prereleaseSeparator === -1 ? withoutBuild : withoutBuild.slice(0, prereleaseSeparator);\n const prereleaseText =\n prereleaseSeparator === -1 ? undefined : withoutBuild.slice(prereleaseSeparator + 1);\n const core = parseCore(coreText);\n if (core === undefined) return undefined;\n const prerelease = prereleaseText === undefined ? [] : parseIdentifiers(prereleaseText, true);\n return prerelease === undefined ? undefined : { core, prerelease };\n}\n\nfunction compareNumeric(left: string, right: string): Comparison {\n const leftValue = BigInt(left);\n const rightValue = BigInt(right);\n if (leftValue < rightValue) return -1;\n if (leftValue > rightValue) return 1;\n return 0;\n}\n\nfunction comparePrereleaseIdentifier(left: string, right: string): Comparison {\n if (left === right) return 0;\n const leftNumeric = onlyAsciiDigits(left);\n const rightNumeric = onlyAsciiDigits(right);\n if (leftNumeric && rightNumeric) return compareNumeric(left, right);\n if (leftNumeric) return -1;\n if (rightNumeric) return 1;\n return left < right ? -1 : 1;\n}\n\nfunction comparePrerelease(left: readonly string[], right: readonly string[]): Comparison {\n if (left.length === 0 && right.length === 0) return 0;\n if (left.length === 0) return 1;\n if (right.length === 0) return -1;\n\n for (let index = 0; index < Math.max(left.length, right.length); index++) {\n const leftIdentifier = left[index];\n if (leftIdentifier === undefined) return -1;\n const rightIdentifier = right[index];\n if (rightIdentifier === undefined) return 1;\n const comparison = comparePrereleaseIdentifier(leftIdentifier, rightIdentifier);\n if (comparison !== 0) return comparison;\n }\n return 0;\n}\n\nfunction compareCore(left: ParsedSemver['core'], right: ParsedSemver['core']): Comparison {\n for (let index = 0; index < 3; index++) {\n const comparison = compareNumeric(left[index] ?? '0', right[index] ?? '0');\n if (comparison !== 0) return comparison;\n }\n return 0;\n}\n\nfunction compareLegacyVersions(left: string, right: string): Comparison {\n const leftParts = left.split('.').map(Number);\n const rightParts = right.split('.').map(Number);\n for (let index = 0; index < 3; index++) {\n const leftValue = leftParts[index] ?? 0;\n const rightValue = rightParts[index] ?? 0;\n if (leftValue < rightValue) return -1;\n if (leftValue > rightValue) return 1;\n }\n return 0;\n}\n\n/**\n * Compare SemVer versions. Legacy non-SemVer inputs retain numeric-core comparison\n * so downgrade protection remains conservative for older project markers.\n *\n * Safety gates must validate both inputs with `isSafePackageVersion` before calling\n * this function: the compatibility fallback cannot distinguish equal numeric cores\n * from wholly unparseable input.\n * @param a\n * @param b\n * @returns -1 if a < b, 0 if a == b, 1 if a > b\n */\nexport function compareVersions(a: string, b: string): Comparison {\n const parsedA = parseSemver(a);\n const parsedB = parseSemver(b);\n if (parsedA === undefined || parsedB === undefined) return compareLegacyVersions(a, b);\n const coreComparison = compareCore(parsedA.core, parsedB.core);\n return coreComparison === 0\n ? comparePrerelease(parsedA.prerelease, parsedB.prerelease)\n : coreComparison;\n}\n\n/** Accept a SemVer-shaped package version before interpolating it into a shell command. */\nexport function isSafePackageVersion(version: string): boolean {\n return parseSemver(version) !== undefined;\n}\n"],"mappings":"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","names":[]}
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-ZESGPJCY.js";
5
5
  import {
6
6
  setupPythonTooling
7
- } from "./chunk-F4CA33GP.js";
7
+ } from "./chunk-IWU7G6G7.js";
8
8
  import {
9
9
  SAFEWORD_PRIMARY_LINT_SCRIPT,
10
10
  detect
@@ -1544,4 +1544,4 @@ export {
1544
1544
  LEFTHOOK_CONFIGS,
1545
1545
  createProjectContext
1546
1546
  };
1547
- //# sourceMappingURL=chunk-KWBGQ2YZ.js.map
1547
+ //# sourceMappingURL=chunk-HKFPZSXT.js.map
@@ -1,9 +1,9 @@
1
- import {
2
- CODEX_MIGRATION_SCHEMA
3
- } from "./chunk-O5JEWG3M.js";
4
1
  import {
5
2
  MCP_SERVERS
6
3
  } from "./chunk-GUIZH3NI.js";
4
+ import {
5
+ CODEX_MIGRATION_SCHEMA
6
+ } from "./chunk-O5JEWG3M.js";
7
7
  import {
8
8
  warn
9
9
  } from "./chunk-DAFOEHJA.js";
@@ -1454,12 +1454,12 @@ var SETTINGS_HOOKS = {
1454
1454
  hook(`bun ${HOOKS_DIR}/session-dependency-readiness.ts`),
1455
1455
  asyncRewakeHook(`bun ${HOOKS_DIR}/session-auto-upgrade.ts`),
1456
1456
  hook(`bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
1457
+ hook(`bun ${HOOKS_DIR}/session-reply-format.ts --agent=claude`),
1457
1458
  hook(`bun ${HOOKS_DIR}/session-version.ts`),
1458
1459
  hook(`bun ${HOOKS_DIR}/session-lint-check.ts`),
1459
1460
  hook(`bun ${HOOKS_DIR}/session-architecture-heal.ts`),
1460
1461
  hook(`bun ${HOOKS_DIR}/session-author-model.ts`),
1461
1462
  hook(`bun ${HOOKS_DIR}/session-start-reentry.ts`),
1462
- matchedHook("compact", `bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
1463
1463
  matchedHook("compact", `bun ${HOOKS_DIR}/session-compact-context.ts`)
1464
1464
  ],
1465
1465
  UserPromptSubmit: [
@@ -2606,8 +2606,14 @@ var SAFEWORD_SCHEMA = {
2606
2606
  ".safeword/hooks/session-safeword-context.ts": {
2607
2607
  template: "hooks/session-safeword-context.ts"
2608
2608
  },
2609
+ ".safeword/hooks/session-reply-format.ts": {
2610
+ template: "hooks/session-reply-format.ts"
2611
+ },
2609
2612
  ".safeword/hooks/session-codex-start.ts": {
2610
- template: "hooks/session-codex-start.ts"
2613
+ template: "hooks/session-codex-start.ts",
2614
+ // Packaged by the CLI for native Codex dispatch, but no longer copied
2615
+ // into projects. Existing copies are legacy migration inputs.
2616
+ generator: () => void 0
2611
2617
  },
2612
2618
  ".safeword/hooks/session-cursor-auto-upgrade.ts": {
2613
2619
  template: "hooks/session-cursor-auto-upgrade.ts"
@@ -3166,11 +3172,9 @@ ${managedPrettierPaths(ctx).join("\n")}
3166
3172
  requires: SHARED_FILING_INVARIANTS
3167
3173
  },
3168
3174
  "packages/cli/templates/hooks/lib/quality.ts": {
3169
- // Cursor's stop hook imports QUALITY_REVIEW_MESSAGE. The export must exist
3170
- // or Cursor users get a broken hook. The four marker strings (CONFIDENT,
3171
- // BLOCKED, Tried:, Need:) define the binary-terminal shape from ticket 143.
3172
- // Removing any of them would silently regress the prompt back to legacy
3173
- // free-form review.
3175
+ // Cursor's stop hook imports QUALITY_REVIEW_MESSAGE. The grammar, renderer,
3176
+ // and evaluator exports keep proactive wording and Stop validation coupled;
3177
+ // requiring individual labels here would create a second grammar source.
3174
3178
  requires: [
3175
3179
  "QUALITY_REVIEW_MESSAGE",
3176
3180
  // prompt-questions.ts imports all three pre-response pointers; the Stop
@@ -3179,10 +3183,9 @@ ${managedPrettierPaths(ctx).join("\n")}
3179
3183
  "REPLY_FORMAT_LEAD_RULE",
3180
3184
  "REPLY_FORMAT_LEAD",
3181
3185
  "REPLY_FORMAT_REMINDER",
3182
- "CONFIDENT",
3183
- "BLOCKED",
3184
- "Tried:",
3185
- "Need:"
3186
+ "DECISION_BRIEF_GRAMMAR",
3187
+ "renderDecisionBriefContract",
3188
+ "evaluateDecisionBriefCompliance"
3186
3189
  ]
3187
3190
  },
3188
3191
  "packages/cli/templates/doc-templates/test-definitions-feature.md": {
@@ -3227,6 +3230,7 @@ export {
3227
3230
  setupPythonTooling,
3228
3231
  hasImportLinterScaffoldTarget,
3229
3232
  generateOwnedPathsModule,
3233
+ SETTINGS_HOOKS,
3230
3234
  typescriptPackages,
3231
3235
  filterOutSafewordHooks,
3232
3236
  BDD_LANE_FILE_PATHS,
@@ -3234,4 +3238,4 @@ export {
3234
3238
  boundaryShimCommand,
3235
3239
  SAFEWORD_SCHEMA
3236
3240
  };
3237
- //# sourceMappingURL=chunk-F4CA33GP.js.map
3241
+ //# sourceMappingURL=chunk-IWU7G6G7.js.map