safeword 0.54.0 → 0.55.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 (57) hide show
  1. package/dist/architecture-CYFAXY2U.js +256 -0
  2. package/dist/architecture-CYFAXY2U.js.map +1 -0
  3. package/dist/{check-IZ42MYCT.js → check-TKA2IIC7.js} +5 -5
  4. package/dist/{chunk-VVWHQBTU.js → chunk-5ES7OYBI.js} +2 -2
  5. package/dist/{chunk-N74UNUZ6.js → chunk-IE32BCVN.js} +4 -4
  6. package/dist/{chunk-SW3D7PN7.js → chunk-JQVVJCLH.js} +10 -3
  7. package/dist/chunk-JQVVJCLH.js.map +1 -0
  8. package/dist/{chunk-YXEKBWNB.js → chunk-SLPSZC2D.js} +2 -2
  9. package/dist/{chunk-VW6VSC5R.js → chunk-VLK2DXJ7.js} +1 -1
  10. package/dist/chunk-VLK2DXJ7.js.map +1 -0
  11. package/dist/{chunk-HN3ZZETN.js → chunk-WJOSBJ37.js} +6 -1
  12. package/dist/{chunk-HN3ZZETN.js.map → chunk-WJOSBJ37.js.map} +1 -1
  13. package/dist/cli.js +15 -9
  14. package/dist/cli.js.map +1 -1
  15. package/dist/{codify-2HGAGRPT.js → codify-64DSX5YK.js} +2 -2
  16. package/dist/{diff-ZCRAOPAC.js → diff-WFF3TQH2.js} +3 -3
  17. package/dist/{diff-ZCRAOPAC.js.map → diff-WFF3TQH2.js.map} +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/presets/typescript/index.js +1 -1
  20. package/dist/{reset-ETS3FAAG.js → reset-GY7TFOIB.js} +3 -3
  21. package/dist/{setup-MCRTMRBD.js → setup-VVQ3EKND.js} +6 -6
  22. package/dist/{sync-learnings-PALEDNJ2.js → sync-learnings-RY6SVKZ2.js} +2 -2
  23. package/dist/{sync-tickets-O6KTBGNT.js → sync-tickets-LMQKAABL.js} +3 -3
  24. package/dist/{ticket-new-W3YOFGQU.js → ticket-new-BK5VDS4X.js} +2 -2
  25. package/dist/{upgrade-RRO7PLMT.js → upgrade-IQNLPR5G.js} +6 -6
  26. package/dist/{upgrade-RRO7PLMT.js.map → upgrade-IQNLPR5G.js.map} +1 -1
  27. package/package.json +1 -1
  28. package/templates/SAFEWORD.md +2 -0
  29. package/templates/commands/audit.md +8 -1
  30. package/templates/hooks/lib/dependency-readiness.ts +228 -20
  31. package/templates/hooks/lib/lint.ts +5 -7
  32. package/templates/hooks/lib/readiness-pointer.ts +18 -0
  33. package/templates/hooks/lib/test-runner.ts +23 -2
  34. package/templates/hooks/post-tool-bypass-warn.ts +1 -1
  35. package/templates/hooks/post-tool-lint.ts +1 -1
  36. package/templates/hooks/pre-tool-dependency-readiness.ts +2 -0
  37. package/templates/hooks/prompt-questions.ts +16 -1
  38. package/templates/hooks/session-architecture-heal.ts +30 -0
  39. package/templates/hooks/session-auto-upgrade.ts +3 -3
  40. package/templates/hooks/session-bun-check.sh +1 -1
  41. package/templates/hooks/session-dependency-readiness.ts +5 -2
  42. package/templates/hooks/stop-quality.ts +22 -0
  43. package/templates/skills/audit/SKILL.md +8 -1
  44. package/templates/skills/bdd/TDD.md +1 -1
  45. package/templates/skills/tdd-review/SKILL.md +29 -23
  46. package/dist/chunk-SW3D7PN7.js.map +0 -1
  47. package/dist/chunk-VW6VSC5R.js.map +0 -1
  48. /package/dist/{check-IZ42MYCT.js.map → check-TKA2IIC7.js.map} +0 -0
  49. /package/dist/{chunk-VVWHQBTU.js.map → chunk-5ES7OYBI.js.map} +0 -0
  50. /package/dist/{chunk-N74UNUZ6.js.map → chunk-IE32BCVN.js.map} +0 -0
  51. /package/dist/{chunk-YXEKBWNB.js.map → chunk-SLPSZC2D.js.map} +0 -0
  52. /package/dist/{codify-2HGAGRPT.js.map → codify-64DSX5YK.js.map} +0 -0
  53. /package/dist/{reset-ETS3FAAG.js.map → reset-GY7TFOIB.js.map} +0 -0
  54. /package/dist/{setup-MCRTMRBD.js.map → setup-VVQ3EKND.js.map} +0 -0
  55. /package/dist/{sync-learnings-PALEDNJ2.js.map → sync-learnings-RY6SVKZ2.js.map} +0 -0
  56. /package/dist/{sync-tickets-O6KTBGNT.js.map → sync-tickets-LMQKAABL.js.map} +0 -0
  57. /package/dist/{ticket-new-W3YOFGQU.js.map → ticket-new-BK5VDS4X.js.map} +0 -0
@@ -0,0 +1,256 @@
1
+ import {
2
+ resolveGeneratedArchitecturePath
3
+ } from "./chunk-WJOSBJ37.js";
4
+ import {
5
+ success
6
+ } from "./chunk-I2GV5QKO.js";
7
+
8
+ // src/commands/architecture.ts
9
+ import process from "process";
10
+
11
+ // src/utils/architecture-document.ts
12
+ import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
13
+ import nodePath3 from "path";
14
+
15
+ // src/utils/architecture-fingerprint.ts
16
+ import { createHash } from "crypto";
17
+ import { readdirSync as readdirSync2, readFileSync } from "fs";
18
+ import nodePath2 from "path";
19
+
20
+ // src/utils/architecture-skeleton.ts
21
+ import { readdirSync } from "fs";
22
+ import nodePath from "path";
23
+ var PURPOSE_PLACEHOLDER = "No description yet \u2014 awaiting prose.";
24
+ function extractSkeleton(projectDirectory) {
25
+ const sourceDirectory = nodePath.join(projectDirectory, "src");
26
+ let entries;
27
+ try {
28
+ entries = readdirSync(sourceDirectory, { withFileTypes: true });
29
+ } catch {
30
+ return { nodes: [] };
31
+ }
32
+ const nodes = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
33
+ name: entry.name,
34
+ // Forward slashes always — the rendered doc and fingerprint must be
35
+ // platform-stable (the fingerprint normalizes paths the same way).
36
+ path: `src/${entry.name}`,
37
+ purpose: PURPOSE_PLACEHOLDER
38
+ })).toSorted((a, b) => a.name.localeCompare(b.name));
39
+ return { nodes };
40
+ }
41
+
42
+ // src/utils/architecture-fingerprint.ts
43
+ var DEPENDENCY_CRUISER_CONFIG_NAMES = [
44
+ ".dependency-cruiser.cjs",
45
+ ".dependency-cruiser.js",
46
+ ".dependency-cruiser.mjs",
47
+ ".dependency-cruiser.json"
48
+ ];
49
+ var SCHEMA_EXTENSIONS = /* @__PURE__ */ new Set([".sql", ".prisma"]);
50
+ var SHAPE_SCAN_EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
51
+ ".git",
52
+ ".project",
53
+ ".safeword",
54
+ "dist",
55
+ "node_modules"
56
+ ]);
57
+ var DEPENDENCY_SECTIONS = [
58
+ "dependencies",
59
+ "devDependencies",
60
+ "peerDependencies",
61
+ "optionalDependencies"
62
+ ];
63
+ var byString = (a, b) => a.localeCompare(b);
64
+ function collectShapeInputs(projectDirectory) {
65
+ const moduleNames = extractSkeleton(projectDirectory).nodes.map((node) => node.name).toSorted(byString);
66
+ return {
67
+ moduleNames,
68
+ dependencyNames: readDependencyNames(projectDirectory),
69
+ boundaryConfig: readBoundaryConfig(projectDirectory),
70
+ schemaFiles: collectSchemaFiles(projectDirectory)
71
+ };
72
+ }
73
+ function shapeFingerprint(projectDirectory) {
74
+ const inputs = collectShapeInputs(projectDirectory);
75
+ return createHash("sha256").update(JSON.stringify(inputs)).digest("hex");
76
+ }
77
+ function readDependencyNames(projectDirectory) {
78
+ const manifest = readJson(nodePath2.join(projectDirectory, "package.json"));
79
+ if (manifest === void 0) return [];
80
+ const names = /* @__PURE__ */ new Set();
81
+ for (const section of DEPENDENCY_SECTIONS) {
82
+ const entry = manifest[section];
83
+ if (entry !== null && typeof entry === "object") {
84
+ for (const name of Object.keys(entry)) names.add(name);
85
+ }
86
+ }
87
+ return [...names].toSorted(byString);
88
+ }
89
+ function readBoundaryConfig(projectDirectory) {
90
+ for (const name of DEPENDENCY_CRUISER_CONFIG_NAMES) {
91
+ try {
92
+ return readFileSync(nodePath2.join(projectDirectory, name), "utf8");
93
+ } catch {
94
+ }
95
+ }
96
+ return "";
97
+ }
98
+ function collectSchemaFiles(projectDirectory) {
99
+ const schemaFiles = [];
100
+ const pending = [projectDirectory];
101
+ while (pending.length > 0) {
102
+ const directory = pending.pop();
103
+ if (directory !== void 0) {
104
+ scanDirectoryForSchema(projectDirectory, directory, schemaFiles, pending);
105
+ }
106
+ }
107
+ return schemaFiles.toSorted(byString);
108
+ }
109
+ function scanDirectoryForSchema(projectDirectory, directory, schemaFiles, pending) {
110
+ let entries;
111
+ try {
112
+ entries = readdirSync2(directory, { withFileTypes: true });
113
+ } catch {
114
+ return;
115
+ }
116
+ for (const entry of entries) {
117
+ if (entry.isDirectory()) {
118
+ if (!SHAPE_SCAN_EXCLUDED_DIRECTORIES.has(entry.name)) {
119
+ pending.push(nodePath2.join(directory, entry.name));
120
+ }
121
+ } else if (SCHEMA_EXTENSIONS.has(nodePath2.extname(entry.name))) {
122
+ const absolutePath = nodePath2.join(directory, entry.name);
123
+ schemaFiles.push(nodePath2.relative(projectDirectory, absolutePath).replaceAll("\\", "/"));
124
+ }
125
+ }
126
+ }
127
+ function readJson(filePath) {
128
+ try {
129
+ return JSON.parse(readFileSync(filePath, "utf8"));
130
+ } catch {
131
+ return void 0;
132
+ }
133
+ }
134
+
135
+ // src/utils/architecture-reconcile.ts
136
+ function reconcileSections(input) {
137
+ const verdicts = input.nodeNames.map((node) => ({
138
+ node,
139
+ status: liveNodeStatus(input.priorStamps[node], input.fingerprint)
140
+ }));
141
+ const present = new Set(input.nodeNames);
142
+ for (const node of Object.keys(input.priorStamps)) {
143
+ if (!present.has(node)) verdicts.push({ node, status: "orphaned" });
144
+ }
145
+ return verdicts;
146
+ }
147
+ function liveNodeStatus(stamp, fingerprint) {
148
+ if (stamp === void 0) return "placeholder";
149
+ if (stamp !== fingerprint) return "stale";
150
+ return "current";
151
+ }
152
+
153
+ // src/utils/architecture-document.ts
154
+ var FINGERPRINT_KEY = "fingerprint";
155
+ var GENERATOR_KEY = "generator";
156
+ var GENERATOR_VALUE = "safeword-architecture";
157
+ function frontmatterBody(content) {
158
+ return /^---\r?\n([\s\S]*?)\r?\n---/.exec(content)?.[1];
159
+ }
160
+ function isSafewordOwned(content) {
161
+ return frontmatterBody(content)?.split(/\r?\n/).includes(`${GENERATOR_KEY}: ${GENERATOR_VALUE}`) ?? false;
162
+ }
163
+ function readDocumentFingerprint(content) {
164
+ const line = frontmatterBody(content)?.split(/\r?\n/).find((candidate) => candidate.startsWith(`${FINGERPRINT_KEY}:`));
165
+ if (line === void 0) return void 0;
166
+ const value = line.slice(FINGERPRINT_KEY.length + 1).trim();
167
+ return value.length > 0 ? value : void 0;
168
+ }
169
+ var RECONCILED_PREFIX = "<!-- reconciled:";
170
+ function selfHeal(projectDirectory) {
171
+ const path = resolveGeneratedArchitecturePath(projectDirectory);
172
+ const fingerprint = shapeFingerprint(projectDirectory);
173
+ const existing = readExisting(path);
174
+ const nodes = extractSkeleton(projectDirectory).nodes;
175
+ const action = decideAction(existing, fingerprint, nodes.length > 0);
176
+ if (action !== "unchanged" && action !== "skipped" && action !== "noop") {
177
+ mkdirSync(nodePath3.dirname(path), { recursive: true });
178
+ const priorStamps = existing === void 0 ? /* @__PURE__ */ new Map() : parseSectionStamps(existing);
179
+ writeFileSync(path, renderDocument(nodes, fingerprint, priorStamps));
180
+ }
181
+ return { action, path };
182
+ }
183
+ function readExisting(path) {
184
+ try {
185
+ return readFileSync2(path, "utf8");
186
+ } catch {
187
+ return void 0;
188
+ }
189
+ }
190
+ function decideAction(existing, fingerprint, hasModules) {
191
+ if (existing === void 0) return hasModules ? "created" : "noop";
192
+ if (!isSafewordOwned(existing)) return "skipped";
193
+ const recorded = readDocumentFingerprint(existing);
194
+ if (recorded === void 0) return "regenerated";
195
+ if (recorded !== fingerprint) return "healed";
196
+ return "unchanged";
197
+ }
198
+ function parseSectionStamps(content) {
199
+ const stamps = /* @__PURE__ */ new Map();
200
+ const pattern = /^### (.+)\n+<!-- reconciled: (\S+) -->/gm;
201
+ for (const match of content.matchAll(pattern)) {
202
+ const name = match[1];
203
+ const stamp = match[2];
204
+ if (name !== void 0 && stamp !== void 0) stamps.set(name.trim(), stamp);
205
+ }
206
+ return stamps;
207
+ }
208
+ function renderDocument(nodes, fingerprint, priorStamps) {
209
+ const verdicts = reconcileSections({
210
+ priorStamps: Object.fromEntries(priorStamps),
211
+ nodeNames: nodes.map((node) => node.name),
212
+ fingerprint
213
+ });
214
+ const nodeByName = new Map(nodes.map((node) => [node.name, node]));
215
+ const sections = verdicts.map((verdict) => {
216
+ const node = nodeByName.get(verdict.node);
217
+ const stamp = priorStamps.get(verdict.node) ?? fingerprint;
218
+ return node === void 0 ? renderOrphanSection(verdict.node) : renderSection(node, stamp, verdict.status);
219
+ }).join("\n");
220
+ return `---
221
+ ${GENERATOR_KEY}: ${GENERATOR_VALUE}
222
+ ${FINGERPRINT_KEY}: ${fingerprint}
223
+ ---
224
+
225
+ # Architecture
226
+
227
+ ## Modules
228
+
229
+ ${sections}`;
230
+ }
231
+ function renderSection(node, stamp, status) {
232
+ const marker = status === "stale" ? "\n> \u26A0 stale: structure changed since this section was reconciled.\n" : "";
233
+ return `### ${node.name}
234
+
235
+ ${RECONCILED_PREFIX} ${stamp} -->
236
+
237
+ \`${node.path}\` \u2014 ${node.purpose}
238
+ ${marker}`;
239
+ }
240
+ function renderOrphanSection(name) {
241
+ return `### ${name}
242
+
243
+ > \u26A0 orphaned: this section describes a module that no longer exists.
244
+ `;
245
+ }
246
+
247
+ // src/commands/architecture.ts
248
+ function architecture(cwd = process.cwd()) {
249
+ const result = selfHeal(cwd);
250
+ success(`Architecture state document ${result.action}: ${result.path}`);
251
+ return Promise.resolve();
252
+ }
253
+ export {
254
+ architecture
255
+ };
256
+ //# sourceMappingURL=architecture-CYFAXY2U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/architecture.ts","../src/utils/architecture-document.ts","../src/utils/architecture-fingerprint.ts","../src/utils/architecture-skeleton.ts","../src/utils/architecture-reconcile.ts"],"sourcesContent":["/**\n * `safeword architecture` — refresh the architecture state document (ticket\n * QD5DTT, Slice 1).\n *\n * Thin CLI entry over `selfHeal`: re-extracts the skeleton and reconciles prose\n * markers at the generated `.project/architecture.generated.md`. The SessionStart hook shells\n * out to this command so the heal logic lives in one place (the CLI), not\n * duplicated into the hook lib.\n */\n\nimport process from 'node:process';\n\nimport { selfHeal } from '../utils/architecture-document.js';\nimport { success } from '../utils/output.js';\n\nexport function architecture(cwd: string = process.cwd()): Promise<void> {\n const result = selfHeal(cwd);\n success(`Architecture state document ${result.action}: ${result.path}`);\n return Promise.resolve();\n}\n","/**\n * Architecture state-document self-heal (ticket QD5DTT, Slice 1).\n *\n * Reads the generated architecture state document at the fixed\n * `<namespace-root>/architecture.generated.md`, compares its recorded\n * shape-fingerprint against the live one, and deterministically\n * (LLM-free) re-extracts the skeleton when they differ — creating the document\n * when absent and regenerating it when its fingerprint is missing or corrupt.\n * This is the SessionStart entry point that keeps structural facts fresh,\n * including after out-of-band human edits.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { shapeFingerprint } from './architecture-fingerprint.js';\nimport { reconcileSections, type SectionStatus } from './architecture-reconcile.js';\nimport { extractSkeleton, type SkeletonNode } from './architecture-skeleton.js';\nimport { resolveGeneratedArchitecturePath } from './configured-paths.js';\n\ntype SelfHealAction = 'created' | 'healed' | 'unchanged' | 'regenerated' | 'skipped' | 'noop';\n\nexport interface SelfHealResult {\n action: SelfHealAction;\n path: string;\n}\n\nconst FINGERPRINT_KEY = 'fingerprint';\n\n/** Frontmatter ownership marker — only documents carrying it are safeword's to rewrite. */\nconst GENERATOR_KEY = 'generator';\nconst GENERATOR_VALUE = 'safeword-architecture';\n\n/** The frontmatter body (between the `---` fences), CRLF-tolerant, or undefined. */\nfunction frontmatterBody(content: string): string | undefined {\n return /^---\\r?\\n([\\s\\S]*?)\\r?\\n---/.exec(content)?.[1];\n}\n\n/**\n * Whether safeword owns this document, i.e. it carries the generator marker.\n * A document without it is hand-authored (or foreign) and must never be\n * overwritten — the marker survives even when the fingerprint is corrupted.\n * Exact-line match so a different generator (e.g. `safeword-architecture-v2`)\n * is not mistaken for this one.\n */\nfunction isSafewordOwned(content: string): boolean {\n return (\n frontmatterBody(content)?.split(/\\r?\\n/).includes(`${GENERATOR_KEY}: ${GENERATOR_VALUE}`) ??\n false\n );\n}\n\n/** Parse the recorded fingerprint from a document's frontmatter, or undefined. */\nexport function readDocumentFingerprint(content: string): string | undefined {\n const line = frontmatterBody(content)\n ?.split(/\\r?\\n/)\n .find(candidate => candidate.startsWith(`${FINGERPRINT_KEY}:`));\n if (line === undefined) return undefined;\n\n const value = line.slice(FINGERPRINT_KEY.length + 1).trim();\n return value.length > 0 ? value : undefined;\n}\n\nconst RECONCILED_PREFIX = '<!-- reconciled:';\n\nexport function selfHeal(projectDirectory: string): SelfHealResult {\n const path = resolveGeneratedArchitecturePath(projectDirectory);\n const fingerprint = shapeFingerprint(projectDirectory);\n const existing = readExisting(path);\n const nodes = extractSkeleton(projectDirectory).nodes;\n const action = decideAction(existing, fingerprint, nodes.length > 0);\n\n if (action !== 'unchanged' && action !== 'skipped' && action !== 'noop') {\n mkdirSync(nodePath.dirname(path), { recursive: true });\n const priorStamps = existing === undefined ? new Map() : parseSectionStamps(existing);\n writeFileSync(path, renderDocument(nodes, fingerprint, priorStamps));\n }\n\n return { action, path };\n}\n\nfunction readExisting(path: string): string | undefined {\n try {\n return readFileSync(path, 'utf8');\n } catch {\n return undefined;\n }\n}\n\nfunction decideAction(\n existing: string | undefined,\n fingerprint: string,\n hasModules: boolean,\n): SelfHealAction {\n // Don't birth an empty doc: a contentless \"## Modules\" implies \"no modules\",\n // which is false for a monorepo the single-repo extractor can't read yet.\n // An existing doc still heals toward empty (orphan markers show real removals).\n if (existing === undefined) return hasModules ? 'created' : 'noop';\n\n // Never touch a document safeword does not own — a hand-written architecture\n // doc has no generator marker and must be left exactly as-is.\n if (!isSafewordOwned(existing)) return 'skipped';\n\n const recorded = readDocumentFingerprint(existing);\n if (recorded === undefined) return 'regenerated';\n if (recorded !== fingerprint) return 'healed';\n return 'unchanged';\n}\n\n/**\n * Map each section's node name to the fingerprint it was last reconciled\n * against, so a heal can preserve prior stamps and mark prose that lags the\n * new structure instead of silently bumping it current.\n */\nfunction parseSectionStamps(content: string): Map<string, string> {\n const stamps = new Map<string, string>();\n const pattern = /^### (.+)\\n+<!-- reconciled: (\\S+) -->/gm;\n\n for (const match of content.matchAll(pattern)) {\n const name = match[1];\n const stamp = match[2];\n if (name !== undefined && stamp !== undefined) stamps.set(name.trim(), stamp);\n }\n\n return stamps;\n}\n\nfunction renderDocument(\n nodes: SkeletonNode[],\n fingerprint: string,\n priorStamps: Map<string, string>,\n): string {\n // reconcileSections is the single source of truth for per-section status;\n // this layer only renders markers from its verdicts.\n const verdicts = reconcileSections({\n priorStamps: Object.fromEntries(priorStamps),\n nodeNames: nodes.map(node => node.name),\n fingerprint,\n });\n const nodeByName = new Map(nodes.map(node => [node.name, node]));\n\n const sections = verdicts\n .map(verdict => {\n const node = nodeByName.get(verdict.node);\n // A section the heal has seen before keeps its prior stamp; a brand-new\n // node is stamped current (a placeholder awaiting prose, not stale).\n const stamp = priorStamps.get(verdict.node) ?? fingerprint;\n return node === undefined\n ? renderOrphanSection(verdict.node)\n : renderSection(node, stamp, verdict.status);\n })\n .join('\\n');\n\n return `---\\n${GENERATOR_KEY}: ${GENERATOR_VALUE}\\n${FINGERPRINT_KEY}: ${fingerprint}\\n---\\n\\n# Architecture\\n\\n## Modules\\n\\n${sections}`;\n}\n\nfunction renderSection(node: SkeletonNode, stamp: string, status: SectionStatus): string {\n const marker =\n status === 'stale' ? '\\n> ⚠ stale: structure changed since this section was reconciled.\\n' : '';\n\n return `### ${node.name}\\n\\n${RECONCILED_PREFIX} ${stamp} -->\\n\\n\\`${node.path}\\` — ${node.purpose}\\n${marker}`;\n}\n\nfunction renderOrphanSection(name: string): string {\n return `### ${name}\\n\\n> ⚠ orphaned: this section describes a module that no longer exists.\\n`;\n}\n","/**\n * Shape-fingerprint of a project's architecture-relevant structure (ticket\n * QD5DTT, Slice 1).\n *\n * Hashes the *shape* — top-level module names, dependency names (not versions),\n * the dependency-cruiser boundary config, and schema files — never source-file\n * bytes. So a structural change moves the fingerprint while semantics-preserving\n * noise (a version bump, a comment edit) does not. This is the cheap, LLM-free\n * drift signal the self-heal path compares against the recorded value.\n */\n\nimport { createHash } from 'node:crypto';\nimport { type Dirent, readdirSync, readFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { extractSkeleton } from './architecture-skeleton.js';\n\n/** Candidate dependency-cruiser config filenames, in resolution order. */\nconst DEPENDENCY_CRUISER_CONFIG_NAMES = [\n '.dependency-cruiser.cjs',\n '.dependency-cruiser.js',\n '.dependency-cruiser.mjs',\n '.dependency-cruiser.json',\n];\n\n/** File extensions treated as schema definitions. */\nconst SCHEMA_EXTENSIONS = new Set(['.sql', '.prisma']);\n\n/** Directories never walked when collecting schema files. */\nconst SHAPE_SCAN_EXCLUDED_DIRECTORIES = new Set([\n '.git',\n '.project',\n '.safeword',\n 'dist',\n 'node_modules',\n]);\n\n/** Dependency manifest sections whose *keys* contribute to the shape. */\nconst DEPENDENCY_SECTIONS = [\n 'dependencies',\n 'devDependencies',\n 'peerDependencies',\n 'optionalDependencies',\n] as const;\n\n/** Stable string ordering for the fingerprint's sorted inputs. */\nconst byString = (a: string, b: string): number => a.localeCompare(b);\n\ninterface ShapeInputs {\n /** Top-level module names. */\n moduleNames: string[];\n /** Dependency names (keys only — versions are deliberately excluded). */\n dependencyNames: string[];\n /** Raw dependency-cruiser boundary config, or '' when none is present. */\n boundaryConfig: string;\n /** Schema file paths, relative to the project root. */\n schemaFiles: string[];\n}\n\nfunction collectShapeInputs(projectDirectory: string): ShapeInputs {\n const moduleNames = extractSkeleton(projectDirectory)\n .nodes.map(node => node.name)\n .toSorted(byString);\n\n return {\n moduleNames,\n dependencyNames: readDependencyNames(projectDirectory),\n boundaryConfig: readBoundaryConfig(projectDirectory),\n schemaFiles: collectSchemaFiles(projectDirectory),\n };\n}\n\nexport function shapeFingerprint(projectDirectory: string): string {\n const inputs = collectShapeInputs(projectDirectory);\n return createHash('sha256').update(JSON.stringify(inputs)).digest('hex');\n}\n\nfunction readDependencyNames(projectDirectory: string): string[] {\n const manifest = readJson(nodePath.join(projectDirectory, 'package.json'));\n if (manifest === undefined) return [];\n\n const names = new Set<string>();\n for (const section of DEPENDENCY_SECTIONS) {\n const entry = manifest[section];\n if (entry !== null && typeof entry === 'object') {\n for (const name of Object.keys(entry)) names.add(name);\n }\n }\n\n return [...names].toSorted(byString);\n}\n\nfunction readBoundaryConfig(projectDirectory: string): string {\n for (const name of DEPENDENCY_CRUISER_CONFIG_NAMES) {\n try {\n return readFileSync(nodePath.join(projectDirectory, name), 'utf8');\n } catch {\n // Try the next candidate name.\n }\n }\n return '';\n}\n\nfunction collectSchemaFiles(projectDirectory: string): string[] {\n const schemaFiles: string[] = [];\n const pending: string[] = [projectDirectory];\n\n while (pending.length > 0) {\n const directory = pending.pop();\n if (directory !== undefined) {\n scanDirectoryForSchema(projectDirectory, directory, schemaFiles, pending);\n }\n }\n\n return schemaFiles.toSorted(byString);\n}\n\nfunction scanDirectoryForSchema(\n projectDirectory: string,\n directory: string,\n schemaFiles: string[],\n pending: string[],\n): void {\n let entries: Dirent[];\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (entry.isDirectory()) {\n if (!SHAPE_SCAN_EXCLUDED_DIRECTORIES.has(entry.name)) {\n pending.push(nodePath.join(directory, entry.name));\n }\n } else if (SCHEMA_EXTENSIONS.has(nodePath.extname(entry.name))) {\n const absolutePath = nodePath.join(directory, entry.name);\n schemaFiles.push(nodePath.relative(projectDirectory, absolutePath).replaceAll('\\\\', '/'));\n }\n }\n}\n\nfunction readJson(filePath: string): Record<string, unknown> | undefined {\n try {\n return JSON.parse(readFileSync(filePath, 'utf8')) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n","/**\n * Deterministic architecture skeleton extractor (ticket QD5DTT, Slice 1).\n *\n * Enumerates the structural facts of a single-repo project — the top-level\n * `src/` modules, each with a code reference and a one-line purpose floor —\n * with zero language-model involvement, so the architecture state doc can\n * never hallucinate structure. The fingerprint and reconcile layers build on\n * the model this returns.\n */\n\nimport { type Dirent, readdirSync } from 'node:fs';\nimport nodePath from 'node:path';\n\n/** Placeholder purpose for a freshly extracted node awaiting human prose. */\nconst PURPOSE_PLACEHOLDER = 'No description yet — awaiting prose.';\n\nexport interface SkeletonNode {\n /** Module name — the top-level `src/` subdirectory. */\n name: string;\n /** Code reference: the module's path relative to the project root. */\n path: string;\n /** One-line purpose (the purpose floor); a placeholder until prose is written. */\n purpose: string;\n}\n\nexport interface Skeleton {\n /** Structural nodes, one per top-level module. */\n nodes: SkeletonNode[];\n}\n\nexport function extractSkeleton(projectDirectory: string): Skeleton {\n const sourceDirectory = nodePath.join(projectDirectory, 'src');\n\n let entries: Dirent[];\n try {\n entries = readdirSync(sourceDirectory, { withFileTypes: true });\n } catch {\n return { nodes: [] };\n }\n\n const nodes = entries\n .filter(entry => entry.isDirectory())\n .map(entry => ({\n name: entry.name,\n // Forward slashes always — the rendered doc and fingerprint must be\n // platform-stable (the fingerprint normalizes paths the same way).\n path: `src/${entry.name}`,\n purpose: PURPOSE_PLACEHOLDER,\n }))\n // Sort by name so the rendered document is deterministic across\n // filesystems (readdirSync order is not guaranteed), like the fingerprint.\n .toSorted((a, b) => a.name.localeCompare(b.name));\n\n return { nodes };\n}\n\n/**\n * The names of nodes that violate the purpose floor — every skeleton node must\n * carry a non-empty one-line purpose. Catches a doc whose purpose was blanked\n * (e.g. hand-edited away), which would otherwise leave the floor unenforced.\n */\nexport function purposeFloorViolations(nodes: SkeletonNode[]): string[] {\n return nodes.filter(node => node.purpose.trim().length === 0).map(node => node.name);\n}\n","/**\n * Reconcile prose sections against the current structure (ticket QD5DTT,\n * Slice 1).\n *\n * Deterministic, LLM-free: the single source of truth for per-section status.\n * Given each section's recorded `reconciled` stamp and the live skeleton, it\n * classifies every node — current, stale, orphaned, or placeholder — and the\n * document layer renders markers from these verdicts. This is what lets the\n * doc be incomplete (prose lagging, visibly marked) yet never silently wrong.\n */\n\nexport type SectionStatus = 'current' | 'stale' | 'orphaned' | 'placeholder';\n\nexport interface ReconcileInput {\n /** Node name → the fingerprint its section was last reconciled against. */\n priorStamps: Record<string, string>;\n /** Names of the nodes in the current skeleton. */\n nodeNames: string[];\n /** The current live skeleton fingerprint. */\n fingerprint: string;\n}\n\nexport interface SectionVerdict {\n node: string;\n status: SectionStatus;\n}\n\nexport function reconcileSections(input: ReconcileInput): SectionVerdict[] {\n const verdicts: SectionVerdict[] = input.nodeNames.map(node => ({\n node,\n status: liveNodeStatus(input.priorStamps[node], input.fingerprint),\n }));\n\n // A prior section whose node is gone is orphaned — surfaced, never dropped.\n const present = new Set(input.nodeNames);\n for (const node of Object.keys(input.priorStamps)) {\n if (!present.has(node)) verdicts.push({ node, status: 'orphaned' });\n }\n\n return verdicts;\n}\n\nfunction liveNodeStatus(stamp: string | undefined, fingerprint: string): SectionStatus {\n // No prior stamp → the node is new this reconcile: a placeholder awaiting\n // prose, not stale. A surviving section is stale exactly when its stamp has\n // fallen behind the live fingerprint.\n if (stamp === undefined) return 'placeholder';\n if (stamp !== fingerprint) return 'stale';\n return 'current';\n}\n"],"mappings":"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","names":["readFileSync","nodePath","readdirSync","nodePath","nodePath","readdirSync","nodePath","readFileSync"]}
@@ -4,15 +4,15 @@ import {
4
4
  import {
5
5
  checkHealth,
6
6
  reportHealthSummary
7
- } from "./chunk-N74UNUZ6.js";
7
+ } from "./chunk-IE32BCVN.js";
8
8
  import {
9
9
  syncTickets
10
- } from "./chunk-VVWHQBTU.js";
10
+ } from "./chunk-5ES7OYBI.js";
11
11
  import "./chunk-NHXVS5FL.js";
12
12
  import "./chunk-YXNI7W5D.js";
13
13
  import "./chunk-XTLCJKGE.js";
14
- import "./chunk-SW3D7PN7.js";
15
- import "./chunk-HN3ZZETN.js";
14
+ import "./chunk-JQVVJCLH.js";
15
+ import "./chunk-WJOSBJ37.js";
16
16
  import "./chunk-GT7KMCFG.js";
17
17
  import "./chunk-PHR2K2Y3.js";
18
18
  import "./chunk-HSC7TELY.js";
@@ -110,4 +110,4 @@ async function check(options) {
110
110
  export {
111
111
  check
112
112
  };
113
- //# sourceMappingURL=check-IZ42MYCT.js.map
113
+ //# sourceMappingURL=check-TKA2IIC7.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-NHXVS5FL.js";
4
4
  import {
5
5
  resolveTicketsDirectory
6
- } from "./chunk-HN3ZZETN.js";
6
+ } from "./chunk-WJOSBJ37.js";
7
7
 
8
8
  // src/ticket-sync/index.ts
9
9
  import { existsSync, readdirSync, readFileSync, writeFileSync } from "fs";
@@ -247,4 +247,4 @@ export {
247
247
  readTickets,
248
248
  syncTickets
249
249
  };
250
- //# sourceMappingURL=chunk-VVWHQBTU.js.map
250
+ //# sourceMappingURL=chunk-5ES7OYBI.js.map
@@ -2,7 +2,7 @@ import {
2
2
  findDanglingDependencies,
3
3
  findTicketsInCycles,
4
4
  readTickets
5
- } from "./chunk-VVWHQBTU.js";
5
+ } from "./chunk-5ES7OYBI.js";
6
6
  import {
7
7
  formatTicketReference
8
8
  } from "./chunk-NHXVS5FL.js";
@@ -22,14 +22,14 @@ import {
22
22
  createProjectContext,
23
23
  getMissingPacks,
24
24
  reconcile
25
- } from "./chunk-SW3D7PN7.js";
25
+ } from "./chunk-JQVVJCLH.js";
26
26
  import {
27
27
  defaultConfiguredPath,
28
28
  readConfiguredDocumentationSources,
29
29
  readConfiguredPath,
30
30
  resolveConfiguredPath,
31
31
  resolveTicketsDirectory
32
- } from "./chunk-HN3ZZETN.js";
32
+ } from "./chunk-WJOSBJ37.js";
33
33
  import {
34
34
  VERSION
35
35
  } from "./chunk-HSC7TELY.js";
@@ -701,4 +701,4 @@ export {
701
701
  checkHealth,
702
702
  reportHealthSummary
703
703
  };
704
- //# sourceMappingURL=chunk-N74UNUZ6.js.map
704
+ //# sourceMappingURL=chunk-IE32BCVN.js.map
@@ -2,7 +2,7 @@ import {
2
2
  NAMESPACE_ROOT_LEGACY,
3
3
  readConfiguredPath,
4
4
  resolveNamespaceRoot
5
- } from "./chunk-HN3ZZETN.js";
5
+ } from "./chunk-WJOSBJ37.js";
6
6
  import {
7
7
  MCP_SERVERS
8
8
  } from "./chunk-GT7KMCFG.js";
@@ -2178,6 +2178,7 @@ var SETTINGS_HOOKS = {
2178
2178
  hook(`bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
2179
2179
  hook(`bun ${HOOKS_DIR}/session-version.ts`),
2180
2180
  hook(`bun ${HOOKS_DIR}/session-lint-check.ts`),
2181
+ hook(`bun ${HOOKS_DIR}/session-architecture-heal.ts`),
2181
2182
  hook(`bun ${HOOKS_DIR}/session-author-model.ts`),
2182
2183
  hook(`bun ${HOOKS_DIR}/session-start-reentry.ts`),
2183
2184
  matchedHook("compact", `bun ${HOOKS_DIR}/session-safeword-context.ts --agent=claude`),
@@ -2592,7 +2593,9 @@ function isHookEntry(h) {
2592
2593
  }
2593
2594
  function isSafewordHook(h) {
2594
2595
  if (!isHookEntry(h)) return false;
2595
- return h.hooks.some((command) => typeof command.command === "string" && command.command.includes(".safeword"));
2596
+ return h.hooks.some(
2597
+ (command) => typeof command.command === "string" && command.command.includes(".safeword")
2598
+ );
2596
2599
  }
2597
2600
  function filterOutSafewordHooks(hooks) {
2598
2601
  return hooks.filter((h) => !isSafewordHook(h));
@@ -2948,6 +2951,7 @@ var SAFEWORD_SCHEMA = {
2948
2951
  ".safeword/hooks/lib/learning-verification-stamps.ts": {
2949
2952
  template: "hooks/lib/learning-verification-stamps.ts"
2950
2953
  },
2954
+ ".safeword/hooks/lib/readiness-pointer.ts": { template: "hooks/lib/readiness-pointer.ts" },
2951
2955
  // Generated at setup/upgrade from SAFEWORD_SCHEMA itself — the prefix list
2952
2956
  // the auto-upgrade hook uses to decide which files to stage. See owned-paths.ts.
2953
2957
  ".safeword/hooks/lib/owned-paths.ts": {
@@ -2966,6 +2970,9 @@ var SAFEWORD_SCHEMA = {
2966
2970
  ".safeword/hooks/session-lint-check.ts": {
2967
2971
  template: "hooks/session-lint-check.ts"
2968
2972
  },
2973
+ ".safeword/hooks/session-architecture-heal.ts": {
2974
+ template: "hooks/session-architecture-heal.ts"
2975
+ },
2969
2976
  ".safeword/hooks/session-author-model.ts": {
2970
2977
  template: "hooks/session-author-model.ts"
2971
2978
  },
@@ -3752,4 +3759,4 @@ export {
3752
3759
  untrackIgnoredFiles,
3753
3760
  createProjectContext
3754
3761
  };
3755
- //# sourceMappingURL=chunk-SW3D7PN7.js.map
3762
+ //# sourceMappingURL=chunk-JQVVJCLH.js.map