safeword 0.56.0 → 0.57.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 (63) hide show
  1. package/dist/{check-OD4QHC2P.js → check-IV6KC65F.js} +10 -4
  2. package/dist/check-IV6KC65F.js.map +1 -0
  3. package/dist/{chunk-64TSNY2M.js → chunk-D5H7VBXQ.js} +2 -2
  4. package/dist/{chunk-RQBSXETC.js → chunk-HGOG5ZLC.js} +75 -7
  5. package/dist/chunk-HGOG5ZLC.js.map +1 -0
  6. package/dist/{chunk-EUSXT3MN.js → chunk-HTDMZQKA.js} +37 -6
  7. package/dist/chunk-HTDMZQKA.js.map +1 -0
  8. package/dist/{chunk-VLK2DXJ7.js → chunk-MT4WBU2P.js} +76 -43
  9. package/dist/chunk-MT4WBU2P.js.map +1 -0
  10. package/dist/{chunk-FZVYR37T.js → chunk-O3QF6QHX.js} +3 -3
  11. package/dist/chunk-ZESHX2BU.js +14 -0
  12. package/dist/chunk-ZESHX2BU.js.map +1 -0
  13. package/dist/cli.js +7 -7
  14. package/dist/{diff-L7DED7WY.js → diff-55Y2SH4U.js} +2 -2
  15. package/dist/index.js +1 -1
  16. package/dist/presets/typescript/index.d.ts +0 -42
  17. package/dist/presets/typescript/index.js +1 -1
  18. package/dist/{reset-LE6GYEYU.js → reset-FZRYTUFF.js} +2 -2
  19. package/dist/{setup-ZJCYBXA6.js → setup-WKFBBSLJ.js} +5 -5
  20. package/dist/{sync-tickets-53DTTY7B.js → sync-tickets-35ZSEKIE.js} +8 -2
  21. package/dist/sync-tickets-35ZSEKIE.js.map +1 -0
  22. package/dist/{sync-tracker-MP6BTZXX.js → sync-tracker-YUXD7QKS.js} +144 -11
  23. package/dist/sync-tracker-YUXD7QKS.js.map +1 -0
  24. package/dist/{upgrade-7JTIBI3I.js → upgrade-MCBH6GTD.js} +5 -5
  25. package/package.json +4 -4
  26. package/templates/commands/audit.md +3 -3
  27. package/templates/commands/refactor.md +1 -1
  28. package/templates/commands/self-review.md +1 -1
  29. package/templates/commands/verify.md +3 -3
  30. package/templates/cursor/rules/safeword-refactoring.mdc +1 -1
  31. package/templates/hooks/codex/pre-tool-quality.ts +11 -2
  32. package/templates/hooks/cursor/after-file-edit.ts +5 -2
  33. package/templates/hooks/cursor/before-shell-execution.ts +55 -0
  34. package/templates/hooks/cursor/gate-adapter.ts +208 -0
  35. package/templates/hooks/cursor/post-tool-quality.ts +70 -0
  36. package/templates/hooks/cursor/pre-tool-quality.ts +101 -0
  37. package/templates/hooks/cursor/stop.ts +5 -2
  38. package/templates/hooks/lib/active-ticket.ts +166 -0
  39. package/templates/hooks/lib/done-gate.ts +172 -0
  40. package/templates/hooks/lib/quality-state.ts +63 -11
  41. package/templates/hooks/lib/run-identity.ts +150 -0
  42. package/templates/hooks/pre-tool-quality.ts +49 -1
  43. package/templates/hooks/prompt-questions.ts +13 -1
  44. package/templates/hooks/record-skill-invocation.ts +22 -15
  45. package/templates/hooks/stop-quality.ts +1 -28
  46. package/templates/hooks/write-review-stamp.ts +4 -2
  47. package/templates/skills/audit/SKILL.md +3 -3
  48. package/templates/skills/quality-review/SKILL.md +3 -3
  49. package/templates/skills/refactor/SKILL.md +34 -11
  50. package/templates/skills/self-review/SKILL.md +1 -1
  51. package/templates/skills/verify/SKILL.md +3 -3
  52. package/dist/check-OD4QHC2P.js.map +0 -1
  53. package/dist/chunk-EUSXT3MN.js.map +0 -1
  54. package/dist/chunk-RQBSXETC.js.map +0 -1
  55. package/dist/chunk-VLK2DXJ7.js.map +0 -1
  56. package/dist/sync-tickets-53DTTY7B.js.map +0 -1
  57. package/dist/sync-tracker-MP6BTZXX.js.map +0 -1
  58. /package/dist/{chunk-64TSNY2M.js.map → chunk-D5H7VBXQ.js.map} +0 -0
  59. /package/dist/{chunk-FZVYR37T.js.map → chunk-O3QF6QHX.js.map} +0 -0
  60. /package/dist/{diff-L7DED7WY.js.map → diff-55Y2SH4U.js.map} +0 -0
  61. /package/dist/{reset-LE6GYEYU.js.map → reset-FZRYTUFF.js.map} +0 -0
  62. /package/dist/{setup-ZJCYBXA6.js.map → setup-WKFBBSLJ.js.map} +0 -0
  63. /package/dist/{upgrade-7JTIBI3I.js.map → upgrade-MCBH6GTD.js.map} +0 -0
@@ -1,17 +1,20 @@
1
+ import {
2
+ buildIndexConflictListMessage
3
+ } from "./chunk-ZESHX2BU.js";
1
4
  import {
2
5
  isNewerVersion
3
6
  } from "./chunk-FJYRWU2V.js";
4
7
  import {
5
8
  checkHealth,
6
9
  reportHealthSummary
7
- } from "./chunk-FZVYR37T.js";
10
+ } from "./chunk-O3QF6QHX.js";
8
11
  import "./chunk-YXNI7W5D.js";
9
12
  import "./chunk-XTLCJKGE.js";
10
- import "./chunk-RQBSXETC.js";
13
+ import "./chunk-HGOG5ZLC.js";
11
14
  import "./chunk-GS3TBFXU.js";
12
15
  import {
13
16
  syncTickets
14
- } from "./chunk-EUSXT3MN.js";
17
+ } from "./chunk-HTDMZQKA.js";
15
18
  import "./chunk-NHXVS5FL.js";
16
19
  import "./chunk-P2IC575P.js";
17
20
  import "./chunk-PHR2K2Y3.js";
@@ -79,6 +82,9 @@ function regenerateTicketIndex(cwd) {
79
82
  if (result.wrote) {
80
83
  info("Regenerated ticket index (INDEX.md / INDEX-completed.md)");
81
84
  }
85
+ if (result.indexConflicts.length > 0) {
86
+ warn(buildIndexConflictListMessage(result.indexConflicts));
87
+ }
82
88
  } catch (error) {
83
89
  if (process.env.DEBUG) {
84
90
  console.error("[check] ticket index regen failed:", error);
@@ -111,4 +117,4 @@ async function check(options) {
111
117
  export {
112
118
  check
113
119
  };
114
- //# sourceMappingURL=check-OD4QHC2P.js.map
120
+ //# sourceMappingURL=check-IV6KC65F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * The config-health core lives in ../health.ts (shared with the setup/upgrade\n * self-verify, ticket 3293WH). This command adds the standalone-only\n * surfaces: npm update-check, version display, and ticket-index refresh.\n */\n\nimport process from 'node:process';\n\nimport { checkHealth, type HealthStatus, reportHealthSummary } from '../health.js';\nimport { syncTickets } from '../ticket-sync/index.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { buildIndexConflictListMessage } from '../utils/ticket-index-warnings.js';\nimport { isNewerVersion } from '../utils/version.js';\n\ninterface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `bunx safeword@latest upgrade` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/**\n * Regenerate the ticket discovery index, swallowing any error — index\n * freshness must never block or fail a health check. Reports only when it\n * actually rewrote a file.\n * @param cwd\n */\nfunction regenerateTicketIndex(cwd: string): void {\n try {\n const result = syncTickets(cwd);\n if (result.wrote) {\n info('Regenerated ticket index (INDEX.md / INDEX-completed.md)');\n }\n if (result.indexConflicts.length > 0) {\n warn(buildIndexConflictListMessage(result.indexConflicts));\n }\n } catch (error: unknown) {\n // Best-effort: index freshness must never fail the health check. Surface\n // under DEBUG, then return — the deliberate swallow point.\n if (process.env.DEBUG) {\n console.error('[check] ticket index regen failed:', error);\n }\n return;\n }\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Keep the ticket discovery index fresh at this checkpoint (best-effort —\n // never fail the health check on index regen). Ticket 1GGD28.\n regenerateTicketIndex(cwd);\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n const hasIssues = reportHealthSummary(health);\n\n if (hasIssues) {\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,OAAO,aAAa;AAgBpB,eAAe,mBAAmB,UAAU,KAAmC;AAC7E,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,YAAY,WAAW,MAAM;AACjC,iBAAW,MAAM;AAAA,IACnB,GAAG,OAAO;AAEV,UAAM,WAAW,MAAM,MAAM,8CAA8C;AAAA,MACzE,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,SAAS;AAEtB,QAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,WAAO,KAAK,WAAW;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAe,mBAAmB,QAAqC;AACrE,OAAK,2BAA2B;AAChC,QAAM,gBAAgB,MAAM,mBAAmB;AAE/C,MAAI,CAAC,eAAe;AAClB,SAAK,uCAAuC;AAC5C;AAAA,EACF;AAEA,SAAO,gBAAgB;AACvB,SAAO,kBAAkB,eAAe,OAAO,YAAY,aAAa;AAExE,MAAI,OAAO,iBAAiB;AAC1B,SAAK,sBAAsB,aAAa,EAAE;AAC1C,SAAK,+CAA+C;AAAA,EACtD,OAAO;AACL,YAAQ,mBAAmB;AAAA,EAC7B;AACF;AAMA,SAAS,sBAAsB,QAA4B;AACzD,MAAI,CAAC,OAAO,eAAgB;AAE5B,MAAI,eAAe,OAAO,YAAY,OAAO,cAAc,GAAG;AAC5D,SAAK,oBAAoB,OAAO,cAAc,yBAAyB,OAAO,UAAU,GAAG;AAC3F,SAAK,4BAA4B;AAAA,EACnC,WAAW,eAAe,OAAO,gBAAgB,OAAO,UAAU,GAAG;AACnE,SAAK;AAAA,qCAAwC;AAC7C;AAAA,MACE,4CAA4C,OAAO,cAAc,QAAQ,OAAO,UAAU;AAAA,IAC5F;AAAA,EACF;AACF;AAQA,SAAS,sBAAsB,KAAmB;AAChD,MAAI;AACF,UAAM,SAAS,YAAY,GAAG;AAC9B,QAAI,OAAO,OAAO;AAChB,WAAK,0DAA0D;AAAA,IACjE;AACA,QAAI,OAAO,eAAe,SAAS,GAAG;AACpC,WAAK,8BAA8B,OAAO,cAAc,CAAC;AAAA,IAC3D;AAAA,EACF,SAAS,OAAgB;AAGvB,QAAI,QAAQ,IAAI,OAAO;AACrB,cAAQ,MAAM,sCAAsC,KAAK;AAAA,IAC3D;AACA;AAAA,EACF;AACF;AAMA,eAAsB,MAAM,SAAsC;AAChE,QAAM,MAAM,QAAQ,IAAI;AAExB,SAAO,uBAAuB;AAE9B,QAAM,SAAS,MAAM,YAAY,GAAG;AAGpC,MAAI,CAAC,OAAO,YAAY;AACtB,SAAK,qDAAqD;AAC1D;AAAA,EACF;AAIA,wBAAsB,GAAG;AAGzB,WAAS,gBAAgB,IAAI,OAAO,UAAU,EAAE;AAChD,WAAS,kBAAkB,OAAO,iBAAiB,IAAI,OAAO,cAAc,KAAK,SAAS;AAG1F,MAAI,QAAQ,SAAS;AACnB,SAAK,uCAAuC;AAAA,EAC9C,OAAO;AACL,UAAM,mBAAmB,MAAM;AAAA,EACjC;AAEA,wBAAsB,MAAM;AAC5B,QAAM,YAAY,oBAAoB,MAAM;AAE5C,MAAI,WAAW;AACb,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;","names":[]}
@@ -3,7 +3,7 @@ import {
3
3
  addInstalledPack,
4
4
  isGitRepo,
5
5
  isPackInstalled
6
- } from "./chunk-RQBSXETC.js";
6
+ } from "./chunk-HGOG5ZLC.js";
7
7
  import {
8
8
  SAFEWORD_PEER_DEPENDENCIES
9
9
  } from "./chunk-HSC7TELY.js";
@@ -392,4 +392,4 @@ export {
392
392
  getEslintPeerMismatchWarning,
393
393
  maybeAutoPatchOrNudge
394
394
  };
395
- //# sourceMappingURL=chunk-64TSNY2M.js.map
395
+ //# sourceMappingURL=chunk-D5H7VBXQ.js.map
@@ -2149,9 +2149,54 @@ ${prettier.configEntry}
2149
2149
  `;
2150
2150
  }
2151
2151
  var CURSOR_HOOKS = {
2152
+ // Observational: injects standing context. Fail-open — a failed inject must not
2153
+ // block the session from starting.
2152
2154
  sessionStart: [{ command: "bun ./.safeword/hooks/session-safeword-context.ts --agent=cursor" }],
2155
+ // NOTE (F2TKR3): there is deliberately NO beforeSubmitPrompt gate. That hook
2156
+ // fires at prompt-send time, where Cursor exposes only the prompt text — no tool
2157
+ // name or file path — so it cannot tell "create test-definitions.md" from "write
2158
+ // application code". A block there is a catch-22: it would stop the very prompt
2159
+ // that asks the agent to create the scenarios. The phase gate lives at the edit
2160
+ // layer below (preToolUse), which is path-aware (META_PATHS lets scenario/meta
2161
+ // files through) and session-bound — exact parity with the Claude side.
2162
+ //
2163
+ // Blocking edit gate. Matcher limits it to the `Write` tool (Cursor's only edit
2164
+ // tool) so it never spawns on Read/Grep/Task. Denies edits when a feature at the
2165
+ // implement phase has no test-definitions.md, and on LOC blast-radius overflow.
2166
+ //
2167
+ // Done gate (AKNWZK): this same hook also enforces the done gate. Cursor's `stop`
2168
+ // cannot block, so closing a ticket is gated here — a Write that flips ticket.md to
2169
+ // `status: done` is denied unless the evidence holds (tests green, verify.md in
2170
+ // scope, scenarios complete). That makes done the only edit that runs the test
2171
+ // suite, so the timeout is raised to cover a full run (the suite self-caps at 60s).
2172
+ preToolUse: [
2173
+ {
2174
+ command: "bun ./.safeword/hooks/cursor/pre-tool-quality.ts",
2175
+ matcher: "Write",
2176
+ failClosed: true,
2177
+ timeout: 90
2178
+ }
2179
+ ],
2180
+ // Blocking commit gate (a REFACTOR commit may not touch test files).
2181
+ beforeShellExecution: [
2182
+ { command: "bun ./.safeword/hooks/cursor/before-shell-execution.ts", failClosed: true }
2183
+ ],
2184
+ // Observational: triggers lint on edited files. Fail-open — a lint crash must
2185
+ // not block the edit.
2153
2186
  afterFileEdit: [{ command: "bun ./.safeword/hooks/cursor/after-file-edit.ts" }],
2154
- stop: [{ command: "bun ./.safeword/hooks/cursor/stop.ts" }]
2187
+ // Observational: maintains the per-session quality state (LOC, commit-clears-gate,
2188
+ // ticket binding) the blocking edit gate reads. Matched to edits + shell only.
2189
+ // Fail-open — if it crashes the gate simply lacks fuel and degrades to allow,
2190
+ // which must never block work.
2191
+ postToolUse: [
2192
+ { command: "bun ./.safeword/hooks/cursor/post-tool-quality.ts", matcher: "Write|Shell" }
2193
+ ],
2194
+ // Observational: nudges a quality review. Cursor `stop` cannot block anyway —
2195
+ // the real done enforcement lives in preToolUse (above). loop_limit:1 is
2196
+ // intentional: this is a one-shot reminder (the hook clears its edit marker after
2197
+ // firing), NOT a drive-to-done loop, so a single auto-continue is enough and a
2198
+ // higher cap would just re-nudge noisily.
2199
+ stop: [{ command: "bun ./.safeword/hooks/cursor/stop.ts", loop_limit: 1 }]
2155
2200
  };
2156
2201
  var HOOKS_DIR = '"$CLAUDE_PROJECT_DIR"/.safeword/hooks';
2157
2202
  function hook(command) {
@@ -2932,9 +2977,11 @@ var SAFEWORD_SCHEMA = {
2932
2977
  ".safeword/hooks/lib/lint.ts": { template: "hooks/lib/lint.ts" },
2933
2978
  ".safeword/hooks/lib/quality.ts": { template: "hooks/lib/quality.ts" },
2934
2979
  ".safeword/hooks/lib/quality-state.ts": { template: "hooks/lib/quality-state.ts" },
2980
+ ".safeword/hooks/lib/run-identity.ts": { template: "hooks/lib/run-identity.ts" },
2935
2981
  ".safeword/hooks/lib/dependency-readiness.ts": {
2936
2982
  template: "hooks/lib/dependency-readiness.ts"
2937
2983
  },
2984
+ ".safeword/hooks/lib/done-gate.ts": { template: "hooks/lib/done-gate.ts" },
2938
2985
  ".safeword/hooks/lib/namespace-root.ts": { template: "hooks/lib/namespace-root.ts" },
2939
2986
  ".safeword/hooks/lib/skill-invocation-log.ts": {
2940
2987
  template: "hooks/lib/skill-invocation-log.ts"
@@ -3261,6 +3308,18 @@ var SAFEWORD_SCHEMA = {
3261
3308
  ".safeword/hooks/cursor/after-file-edit.ts": {
3262
3309
  template: "hooks/cursor/after-file-edit.ts"
3263
3310
  },
3311
+ ".safeword/hooks/cursor/gate-adapter.ts": {
3312
+ template: "hooks/cursor/gate-adapter.ts"
3313
+ },
3314
+ ".safeword/hooks/cursor/pre-tool-quality.ts": {
3315
+ template: "hooks/cursor/pre-tool-quality.ts"
3316
+ },
3317
+ ".safeword/hooks/cursor/before-shell-execution.ts": {
3318
+ template: "hooks/cursor/before-shell-execution.ts"
3319
+ },
3320
+ ".safeword/hooks/cursor/post-tool-quality.ts": {
3321
+ template: "hooks/cursor/post-tool-quality.ts"
3322
+ },
3264
3323
  ".safeword/hooks/cursor/stop.ts": { template: "hooks/cursor/stop.ts" }
3265
3324
  },
3266
3325
  // Files created if missing, updated only if content matches current template
@@ -3349,7 +3408,15 @@ var SAFEWORD_SCHEMA = {
3349
3408
  // form is `.jsonc`; yaml/cjs/mjs variants fall back to manual wiring.
3350
3409
  ".markdownlint-cli2.jsonc": MARKDOWNLINT_CLI2_IGNORES_MERGE,
3351
3410
  ".cursor/hooks.json": {
3352
- keys: ["version", "hooks.sessionStart", "hooks.afterFileEdit", "hooks.stop"],
3411
+ keys: [
3412
+ "version",
3413
+ "hooks.sessionStart",
3414
+ "hooks.preToolUse",
3415
+ "hooks.beforeShellExecution",
3416
+ "hooks.afterFileEdit",
3417
+ "hooks.postToolUse",
3418
+ "hooks.stop"
3419
+ ],
3353
3420
  removeFileIfEmpty: true,
3354
3421
  merge: (existing) => {
3355
3422
  const hooks = existing.hooks ?? {};
@@ -3365,10 +3432,11 @@ var SAFEWORD_SCHEMA = {
3365
3432
  },
3366
3433
  unmerge: (existing) => {
3367
3434
  const result = { ...existing };
3368
- const hooks = { ...existing.hooks };
3369
- delete hooks.sessionStart;
3370
- delete hooks.afterFileEdit;
3371
- delete hooks.stop;
3435
+ const existingHooks = existing.hooks ?? {};
3436
+ const safewordHookNames = new Set(Object.keys(CURSOR_HOOKS));
3437
+ const hooks = Object.fromEntries(
3438
+ Object.entries(existingHooks).filter(([name]) => !safewordHookNames.has(name))
3439
+ );
3372
3440
  if (!assignOrPrune(result, "hooks", hooks)) {
3373
3441
  delete result.version;
3374
3442
  }
@@ -3778,4 +3846,4 @@ export {
3778
3846
  untrackIgnoredFiles,
3779
3847
  createProjectContext
3780
3848
  };
3781
- //# sourceMappingURL=chunk-RQBSXETC.js.map
3849
+ //# sourceMappingURL=chunk-HGOG5ZLC.js.map