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.
- package/dist/architecture-CYFAXY2U.js +256 -0
- package/dist/architecture-CYFAXY2U.js.map +1 -0
- package/dist/{check-IZ42MYCT.js → check-TKA2IIC7.js} +5 -5
- package/dist/{chunk-VVWHQBTU.js → chunk-5ES7OYBI.js} +2 -2
- package/dist/{chunk-N74UNUZ6.js → chunk-IE32BCVN.js} +4 -4
- package/dist/{chunk-SW3D7PN7.js → chunk-JQVVJCLH.js} +10 -3
- package/dist/chunk-JQVVJCLH.js.map +1 -0
- package/dist/{chunk-YXEKBWNB.js → chunk-SLPSZC2D.js} +2 -2
- package/dist/{chunk-VW6VSC5R.js → chunk-VLK2DXJ7.js} +1 -1
- package/dist/chunk-VLK2DXJ7.js.map +1 -0
- package/dist/{chunk-HN3ZZETN.js → chunk-WJOSBJ37.js} +6 -1
- package/dist/{chunk-HN3ZZETN.js.map → chunk-WJOSBJ37.js.map} +1 -1
- package/dist/cli.js +15 -9
- package/dist/cli.js.map +1 -1
- package/dist/{codify-2HGAGRPT.js → codify-64DSX5YK.js} +2 -2
- package/dist/{diff-ZCRAOPAC.js → diff-WFF3TQH2.js} +3 -3
- package/dist/{diff-ZCRAOPAC.js.map → diff-WFF3TQH2.js.map} +1 -1
- package/dist/index.js +1 -1
- package/dist/presets/typescript/index.js +1 -1
- package/dist/{reset-ETS3FAAG.js → reset-GY7TFOIB.js} +3 -3
- package/dist/{setup-MCRTMRBD.js → setup-VVQ3EKND.js} +6 -6
- package/dist/{sync-learnings-PALEDNJ2.js → sync-learnings-RY6SVKZ2.js} +2 -2
- package/dist/{sync-tickets-O6KTBGNT.js → sync-tickets-LMQKAABL.js} +3 -3
- package/dist/{ticket-new-W3YOFGQU.js → ticket-new-BK5VDS4X.js} +2 -2
- package/dist/{upgrade-RRO7PLMT.js → upgrade-IQNLPR5G.js} +6 -6
- package/dist/{upgrade-RRO7PLMT.js.map → upgrade-IQNLPR5G.js.map} +1 -1
- package/package.json +1 -1
- package/templates/SAFEWORD.md +2 -0
- package/templates/commands/audit.md +8 -1
- package/templates/hooks/lib/dependency-readiness.ts +228 -20
- package/templates/hooks/lib/lint.ts +5 -7
- package/templates/hooks/lib/readiness-pointer.ts +18 -0
- package/templates/hooks/lib/test-runner.ts +23 -2
- package/templates/hooks/post-tool-bypass-warn.ts +1 -1
- package/templates/hooks/post-tool-lint.ts +1 -1
- package/templates/hooks/pre-tool-dependency-readiness.ts +2 -0
- package/templates/hooks/prompt-questions.ts +16 -1
- package/templates/hooks/session-architecture-heal.ts +30 -0
- package/templates/hooks/session-auto-upgrade.ts +3 -3
- package/templates/hooks/session-bun-check.sh +1 -1
- package/templates/hooks/session-dependency-readiness.ts +5 -2
- package/templates/hooks/stop-quality.ts +22 -0
- package/templates/skills/audit/SKILL.md +8 -1
- package/templates/skills/bdd/TDD.md +1 -1
- package/templates/skills/tdd-review/SKILL.md +29 -23
- package/dist/chunk-SW3D7PN7.js.map +0 -1
- package/dist/chunk-VW6VSC5R.js.map +0 -1
- /package/dist/{check-IZ42MYCT.js.map → check-TKA2IIC7.js.map} +0 -0
- /package/dist/{chunk-VVWHQBTU.js.map → chunk-5ES7OYBI.js.map} +0 -0
- /package/dist/{chunk-N74UNUZ6.js.map → chunk-IE32BCVN.js.map} +0 -0
- /package/dist/{chunk-YXEKBWNB.js.map → chunk-SLPSZC2D.js.map} +0 -0
- /package/dist/{codify-2HGAGRPT.js.map → codify-64DSX5YK.js.map} +0 -0
- /package/dist/{reset-ETS3FAAG.js.map → reset-GY7TFOIB.js.map} +0 -0
- /package/dist/{setup-MCRTMRBD.js.map → setup-VVQ3EKND.js.map} +0 -0
- /package/dist/{sync-learnings-PALEDNJ2.js.map → sync-learnings-RY6SVKZ2.js.map} +0 -0
- /package/dist/{sync-tickets-O6KTBGNT.js.map → sync-tickets-LMQKAABL.js.map} +0 -0
- /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-
|
|
7
|
+
} from "./chunk-IE32BCVN.js";
|
|
8
8
|
import {
|
|
9
9
|
syncTickets
|
|
10
|
-
} from "./chunk-
|
|
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-
|
|
15
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
250
|
+
//# sourceMappingURL=chunk-5ES7OYBI.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
findDanglingDependencies,
|
|
3
3
|
findTicketsInCycles,
|
|
4
4
|
readTickets
|
|
5
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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(
|
|
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-
|
|
3762
|
+
//# sourceMappingURL=chunk-JQVVJCLH.js.map
|