safeword 0.63.0 → 0.67.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-T4RR7Q5J.js → architecture-K67T4PSI.js} +282 -120
- package/dist/architecture-K67T4PSI.js.map +1 -0
- package/dist/{check-MUL6YRIO.js → check-IARKNMMO.js} +11 -9
- package/dist/{check-MUL6YRIO.js.map → check-IARKNMMO.js.map} +1 -1
- package/dist/{chunk-FTMQT2V3.js → chunk-CYICTM6W.js} +21 -2
- package/dist/chunk-CYICTM6W.js.map +1 -0
- package/dist/{chunk-OE6HYAWU.js → chunk-I2C5SGFB.js} +4 -4
- package/dist/chunk-I2C5SGFB.js.map +1 -0
- package/dist/{chunk-DN67MPYH.js → chunk-ILDGIT74.js} +19 -14
- package/dist/chunk-ILDGIT74.js.map +1 -0
- package/dist/{chunk-6BEAVNR2.js → chunk-JQZCESMU.js} +7 -5
- package/dist/chunk-JQZCESMU.js.map +1 -0
- package/dist/chunk-M4FTAAXQ.js +24 -0
- package/dist/chunk-M4FTAAXQ.js.map +1 -0
- package/dist/{chunk-7F523R3I.js → chunk-MZZLPZDQ.js} +462 -312
- package/dist/chunk-MZZLPZDQ.js.map +1 -0
- package/dist/chunk-NDBLQB7Z.js +50 -0
- package/dist/chunk-NDBLQB7Z.js.map +1 -0
- package/dist/{chunk-ZESHX2BU.js → chunk-SEDNCRMM.js} +1 -1
- package/dist/chunk-SEDNCRMM.js.map +1 -0
- package/dist/{chunk-LBFUREUF.js → chunk-TJWIGDMT.js} +6 -4
- package/dist/chunk-TJWIGDMT.js.map +1 -0
- package/dist/{chunk-ZBFYIJNE.js → chunk-VDHXT7SV.js} +145 -23
- package/dist/chunk-VDHXT7SV.js.map +1 -0
- package/dist/{chunk-2KELQTLE.js → chunk-X26EJNPZ.js} +21 -1
- package/dist/chunk-X26EJNPZ.js.map +1 -0
- package/dist/{chunk-CYITXCZO.js → chunk-YJ6BSUPX.js} +5 -3
- package/dist/chunk-YJ6BSUPX.js.map +1 -0
- package/dist/{chunk-YYBLVWZZ.js → chunk-ZPH6ECOQ.js} +40 -1
- package/dist/chunk-ZPH6ECOQ.js.map +1 -0
- package/dist/cli.js +26 -21
- package/dist/cli.js.map +1 -1
- package/dist/{codify-SPIWSH2R.js → codify-QC6UXHI5.js} +20 -9
- package/dist/codify-QC6UXHI5.js.map +1 -0
- package/dist/{diff-BZOC4JXC.js → diff-452OHB47.js} +5 -4
- package/dist/{diff-BZOC4JXC.js.map → diff-452OHB47.js.map} +1 -1
- package/dist/{github-rest-3BZDDOX6.js → github-rest-XHCZPXMW.js} +5 -2
- package/dist/github-rest-XHCZPXMW.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/{lint-gherkin-RKLOOSNO.js → lint-gherkin-KFWEX4LL.js} +5 -4
- package/dist/{lint-gherkin-RKLOOSNO.js.map → lint-gherkin-KFWEX4LL.js.map} +1 -1
- package/dist/presets/typescript/index.js +1 -1
- package/dist/{reset-646VL6X4.js → reset-2N55QL4F.js} +6 -5
- package/dist/{reset-646VL6X4.js.map → reset-2N55QL4F.js.map} +1 -1
- package/dist/{retro-GGVK4BYL.js → retro-7MVICR6B.js} +2 -2
- package/dist/{setup-Q4Y7Y6PN.js → setup-7SYJNJKR.js} +19 -17
- package/dist/setup-7SYJNJKR.js.map +1 -0
- package/dist/{sync-config-VJFZJATR.js → sync-config-P3KXXKTR.js} +4 -3
- package/dist/{sync-learnings-THGNXZCT.js → sync-learnings-TA7F3OYP.js} +3 -3
- package/dist/{sync-tickets-P2FFD7DH.js → sync-tickets-7CW3FE3D.js} +5 -5
- package/dist/{sync-tracker-SV6AEV2O.js → sync-tracker-3S3HZMRX.js} +4 -4
- package/dist/sync-tracker-3S3HZMRX.js.map +1 -0
- package/dist/{test-plan-VMQRP2WF.js → test-plan-M7HEJIVM.js} +103 -20
- package/dist/test-plan-M7HEJIVM.js.map +1 -0
- package/dist/ticket-new-RNS5RTUK.js +357 -0
- package/dist/ticket-new-RNS5RTUK.js.map +1 -0
- package/dist/{upgrade-R52BFPBM.js → upgrade-KEEDWKDK.js} +15 -13
- package/dist/upgrade-KEEDWKDK.js.map +1 -0
- package/package.json +21 -20
- package/templates/SAFEWORD.md +21 -27
- package/templates/commands/self-review.md +3 -3
- package/templates/doc-templates/ticket-template.md +1 -1
- package/templates/guides/architecture-guide.md +15 -11
- package/templates/guides/context-files-guide.md +9 -14
- package/templates/guides/data-architecture-guide.md +9 -17
- package/templates/guides/design-doc-guide.md +7 -17
- package/templates/guides/learning-extraction.md +23 -112
- package/templates/guides/llm-evals-guide.md +14 -40
- package/templates/guides/llm-writing-guide.md +49 -27
- package/templates/guides/planning-guide.md +8 -7
- package/templates/guides/retro.md +15 -8
- package/templates/guides/self-report-filing.md +14 -6
- package/templates/guides/testing-guide.md +28 -37
- package/templates/guides/verification-lanes-guide.md +14 -31
- package/templates/guides/zombie-process-cleanup.md +1 -37
- package/templates/hooks/codex/stop.ts +1 -2
- package/templates/hooks/cursor/gate-adapter.ts +5 -2
- package/templates/hooks/lib/active-ticket.ts +6 -6
- package/templates/hooks/lib/architecture-document-nudge.ts +65 -17
- package/templates/hooks/lib/jtbd.ts +35 -21
- package/templates/hooks/lib/namespace-root.ts +15 -4
- package/templates/hooks/lib/parse-annotation.ts +1 -3
- package/templates/hooks/lib/phase-provenance.ts +175 -11
- package/templates/hooks/lib/process-kill-guard.ts +99 -0
- package/templates/hooks/lib/quality.ts +1 -6
- package/templates/hooks/lib/update-cache.ts +1 -3
- package/templates/hooks/lib/work-log-stamp.ts +84 -0
- package/templates/hooks/post-tool-quality.ts +4 -1
- package/templates/hooks/post-tool-work-log.ts +60 -0
- package/templates/hooks/pre-tool-quality.ts +33 -14
- package/templates/hooks/prompt-questions.ts +2 -2
- package/templates/hooks/stop-quality.ts +1 -1
- package/templates/prompts/architecture.md +3 -1
- package/templates/skills/audit/SKILL.md +66 -26
- package/templates/skills/bdd/DISCOVERY.md +24 -28
- package/templates/skills/bdd/DONE.md +1 -1
- package/templates/skills/bdd/SCENARIOS.md +38 -45
- package/templates/skills/bdd/SKILL.md +5 -31
- package/templates/skills/bdd/SPLITTING.md +3 -15
- package/templates/skills/bdd/TDD.md +9 -11
- package/templates/skills/bdd/VERIFY.md +2 -6
- package/templates/skills/brainstorm/SKILL.md +1 -1
- package/templates/skills/debug/SKILL.md +5 -10
- package/templates/skills/elicit/SKILL.md +2 -2
- package/templates/skills/figure-it-out/SKILL.md +3 -3
- package/templates/skills/quality-review/SKILL.md +5 -12
- package/templates/skills/refactor/SKILL.md +12 -26
- package/templates/skills/retro/SKILL.md +2 -1
- package/templates/skills/review-spec/SKILL.md +2 -2
- package/templates/skills/self-review/SKILL.md +4 -4
- package/templates/skills/tdd-review/SKILL.md +2 -12
- package/templates/skills/testing/SKILL.md +1 -1
- package/templates/skills/ticket-system/SKILL.md +23 -19
- package/templates/skills/verify/SKILL.md +33 -14
- package/templates/spec-template.md +11 -9
- package/dist/architecture-T4RR7Q5J.js.map +0 -1
- package/dist/chunk-2KELQTLE.js.map +0 -1
- package/dist/chunk-6BEAVNR2.js.map +0 -1
- package/dist/chunk-7F523R3I.js.map +0 -1
- package/dist/chunk-CYITXCZO.js.map +0 -1
- package/dist/chunk-DN67MPYH.js.map +0 -1
- package/dist/chunk-FTMQT2V3.js.map +0 -1
- package/dist/chunk-LBFUREUF.js.map +0 -1
- package/dist/chunk-OE6HYAWU.js.map +0 -1
- package/dist/chunk-YYBLVWZZ.js.map +0 -1
- package/dist/chunk-ZBFYIJNE.js.map +0 -1
- package/dist/chunk-ZESHX2BU.js.map +0 -1
- package/dist/codify-SPIWSH2R.js.map +0 -1
- package/dist/github-rest-3BZDDOX6.js.map +0 -1
- package/dist/setup-Q4Y7Y6PN.js.map +0 -1
- package/dist/sync-tracker-SV6AEV2O.js.map +0 -1
- package/dist/test-plan-VMQRP2WF.js.map +0 -1
- package/dist/ticket-new-K4X2ZO6V.js +0 -219
- package/dist/ticket-new-K4X2ZO6V.js.map +0 -1
- package/dist/upgrade-R52BFPBM.js.map +0 -1
- /package/dist/{retro-GGVK4BYL.js.map → retro-7MVICR6B.js.map} +0 -0
- /package/dist/{sync-config-VJFZJATR.js.map → sync-config-P3KXXKTR.js.map} +0 -0
- /package/dist/{sync-learnings-THGNXZCT.js.map → sync-learnings-TA7F3OYP.js.map} +0 -0
- /package/dist/{sync-tickets-P2FFD7DH.js.map → sync-tickets-7CW3FE3D.js.map} +0 -0
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listArchitectureRecords
|
|
3
|
+
} from "./chunk-M4FTAAXQ.js";
|
|
1
4
|
import {
|
|
2
5
|
GENERATED_ARCHITECTURE_FILENAME,
|
|
3
6
|
isArchitectureDocumentEnforcementEnabled,
|
|
7
|
+
resolveArchitectureNarrative,
|
|
4
8
|
resolveGeneratedArchitecturePath
|
|
5
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-CYICTM6W.js";
|
|
6
10
|
import {
|
|
7
11
|
exists,
|
|
12
|
+
findAllInTree,
|
|
8
13
|
isDirectory,
|
|
9
14
|
readFileSafe,
|
|
10
15
|
readJson
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-X26EJNPZ.js";
|
|
12
17
|
import {
|
|
13
18
|
error,
|
|
14
19
|
success,
|
|
@@ -32,6 +37,107 @@ import nodePath3 from "path";
|
|
|
32
37
|
// src/utils/architecture-skeleton.ts
|
|
33
38
|
import { readdirSync } from "fs";
|
|
34
39
|
import nodePath from "path";
|
|
40
|
+
|
|
41
|
+
// src/utils/toml.ts
|
|
42
|
+
function readTomlTableArray(content, table, key) {
|
|
43
|
+
const found = tableArrayBody(content.split(/\r?\n/), table, key);
|
|
44
|
+
if (found === void 0) return void 0;
|
|
45
|
+
const values = found.body.matchAll(/"([^"]*)"|'([^']*)'/g).map((match) => match[1] ?? match[2] ?? "").filter((value) => value.length > 0).toArray();
|
|
46
|
+
return values.length > 0 ? values : void 0;
|
|
47
|
+
}
|
|
48
|
+
function isTomlTableEmptyArray(content, table, key) {
|
|
49
|
+
const found = tableArrayBody(content.split(/\r?\n/), table, key);
|
|
50
|
+
return found !== void 0 && found.closed && found.body.replaceAll(/[\s,]/g, "") === "";
|
|
51
|
+
}
|
|
52
|
+
function hasTomlTable(content, table) {
|
|
53
|
+
return content.split(/\r?\n/).some((line) => tableHeader(stripTomlComment(line).trim()) === table);
|
|
54
|
+
}
|
|
55
|
+
function* linesInTable(content, table) {
|
|
56
|
+
let inTable = false;
|
|
57
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
58
|
+
const line = stripTomlComment(rawLine).trim();
|
|
59
|
+
if (line === "") continue;
|
|
60
|
+
const header = tableHeader(line);
|
|
61
|
+
if (header !== void 0) {
|
|
62
|
+
inTable = header === table;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (inTable) yield line;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function hasTomlTableKey(content, table, key) {
|
|
69
|
+
for (const line of linesInTable(content, table)) {
|
|
70
|
+
if (tableValue(line, key) !== void 0) return true;
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
function readTomlTableString(content, table, key) {
|
|
75
|
+
for (const line of linesInTable(content, table)) {
|
|
76
|
+
const value = tableValue(line, key);
|
|
77
|
+
if (value?.startsWith('"')) {
|
|
78
|
+
const end = value.indexOf('"', 1);
|
|
79
|
+
if (end !== -1) return value.slice(1, end);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function tableArrayBody(lines, table, key) {
|
|
85
|
+
const opened = findTableArrayOpen(lines, table, key);
|
|
86
|
+
if (opened === void 0) return void 0;
|
|
87
|
+
let body = opened.initial;
|
|
88
|
+
for (let index = opened.line; index < lines.length; index += 1) {
|
|
89
|
+
if (index > opened.line) body += `
|
|
90
|
+
${stripTomlComment(lines[index] ?? "")}`;
|
|
91
|
+
const close = indexOfOutsideQuotes(body, "]");
|
|
92
|
+
if (close !== -1) return { body: body.slice(0, close), closed: true };
|
|
93
|
+
}
|
|
94
|
+
return { body, closed: false };
|
|
95
|
+
}
|
|
96
|
+
function findTableArrayOpen(lines, table, key) {
|
|
97
|
+
let inTable = false;
|
|
98
|
+
for (const [index, rawLine] of lines.entries()) {
|
|
99
|
+
const trimmed = stripTomlComment(rawLine).trim();
|
|
100
|
+
const header = tableHeader(trimmed);
|
|
101
|
+
if (header !== void 0) {
|
|
102
|
+
inTable = header === table;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!inTable) continue;
|
|
106
|
+
const value = tableValue(trimmed, key);
|
|
107
|
+
if (value?.startsWith("[") === true) return { line: index, initial: value.slice(1) };
|
|
108
|
+
}
|
|
109
|
+
return void 0;
|
|
110
|
+
}
|
|
111
|
+
function indexOfOutsideQuotes(text, target) {
|
|
112
|
+
let quote = "";
|
|
113
|
+
let index = 0;
|
|
114
|
+
while (index < text.length) {
|
|
115
|
+
const character = text[index];
|
|
116
|
+
if (quote === "") {
|
|
117
|
+
if (character === '"' || character === "'") quote = character;
|
|
118
|
+
else if (character === target) return index;
|
|
119
|
+
} else if (character === quote) {
|
|
120
|
+
quote = "";
|
|
121
|
+
}
|
|
122
|
+
index += 1;
|
|
123
|
+
}
|
|
124
|
+
return -1;
|
|
125
|
+
}
|
|
126
|
+
function tableValue(line, key) {
|
|
127
|
+
const equals = line.indexOf("=");
|
|
128
|
+
if (equals === -1 || line.slice(0, equals).trim() !== key) return void 0;
|
|
129
|
+
return line.slice(equals + 1).trim();
|
|
130
|
+
}
|
|
131
|
+
function tableHeader(line) {
|
|
132
|
+
const match = /^\[\[?([^[\]]+)\]\]?\s*$/.exec(line);
|
|
133
|
+
return match?.[1] === void 0 ? void 0 : match[1].trim();
|
|
134
|
+
}
|
|
135
|
+
function stripTomlComment(line) {
|
|
136
|
+
const hash = indexOfOutsideQuotes(line, "#");
|
|
137
|
+
return hash === -1 ? line : line.slice(0, hash);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/utils/architecture-skeleton.ts
|
|
35
141
|
var PURPOSE_PLACEHOLDER = "No description yet \u2014 awaiting prose.";
|
|
36
142
|
var GO_LAYOUT_DIRECTORIES = ["cmd", "internal", "pkg"];
|
|
37
143
|
var RUST_ROOT_FILES = /* @__PURE__ */ new Set(["lib.rs", "main.rs"]);
|
|
@@ -42,6 +148,7 @@ var PYTHON_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
|
42
148
|
"__init__.py",
|
|
43
149
|
"__main__.py"
|
|
44
150
|
]);
|
|
151
|
+
var JS_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
45
152
|
var byNodeName = (a, b) => a.name.localeCompare(b.name);
|
|
46
153
|
function extractSkeleton(projectDirectory) {
|
|
47
154
|
if (exists(nodePath.join(projectDirectory, "pyproject.toml"))) {
|
|
@@ -51,7 +158,7 @@ function extractSkeleton(projectDirectory) {
|
|
|
51
158
|
if (exists(nodePath.join(projectDirectory, "Cargo.toml"))) {
|
|
52
159
|
return { nodes: rustModuleNodes(projectDirectory) };
|
|
53
160
|
}
|
|
54
|
-
const sourceNodes =
|
|
161
|
+
const sourceNodes = enumerateJsSourceRoot(
|
|
55
162
|
nodePath.join(projectDirectory, "src"),
|
|
56
163
|
(name) => `src/${name}`
|
|
57
164
|
);
|
|
@@ -59,16 +166,70 @@ function extractSkeleton(projectDirectory) {
|
|
|
59
166
|
if (exists(nodePath.join(projectDirectory, "go.mod"))) {
|
|
60
167
|
return { nodes: goLayoutNodes(projectDirectory) };
|
|
61
168
|
}
|
|
62
|
-
|
|
169
|
+
const libraryNodes = enumerateJsSourceRoot(
|
|
170
|
+
nodePath.join(projectDirectory, "lib"),
|
|
171
|
+
(name) => `lib/${name}`
|
|
172
|
+
);
|
|
173
|
+
if (libraryNodes.length > 0) return { nodes: libraryNodes };
|
|
174
|
+
if (declaresWorkspaces(projectDirectory)) return { nodes: [] };
|
|
175
|
+
return { nodes: topLevelJsModuleNodes(projectDirectory) };
|
|
176
|
+
}
|
|
177
|
+
function declaresWorkspaces(projectDirectory) {
|
|
178
|
+
if (exists(nodePath.join(projectDirectory, "pnpm-workspace.yaml"))) return true;
|
|
179
|
+
if (exists(nodePath.join(projectDirectory, "go.work"))) return true;
|
|
180
|
+
const manifest = readJson(nodePath.join(projectDirectory, "package.json"));
|
|
181
|
+
if (manifest !== null && typeof manifest === "object" && manifest.workspaces !== void 0) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
const pyproject = readFileSafe(nodePath.join(projectDirectory, "pyproject.toml"));
|
|
185
|
+
return pyproject !== void 0 && hasTomlTable(pyproject, "tool.uv.workspace");
|
|
63
186
|
}
|
|
64
|
-
function
|
|
187
|
+
function enumerateJsSourceRoot(directory, pathFor) {
|
|
65
188
|
let entries;
|
|
66
189
|
try {
|
|
67
190
|
entries = readdirSync(directory, { withFileTypes: true });
|
|
68
191
|
} catch {
|
|
69
192
|
return [];
|
|
70
193
|
}
|
|
71
|
-
|
|
194
|
+
const directories = entries.filter((entry) => entry.isDirectory());
|
|
195
|
+
if (directories.length > 0) {
|
|
196
|
+
return directories.map((entry) => ({ name: entry.name, path: pathFor(entry.name), purpose: PURPOSE_PLACEHOLDER })).toSorted(byNodeName);
|
|
197
|
+
}
|
|
198
|
+
return jsFileNodes(entries, pathFor);
|
|
199
|
+
}
|
|
200
|
+
function topLevelJsModuleNodes(projectDirectory) {
|
|
201
|
+
let entries;
|
|
202
|
+
try {
|
|
203
|
+
entries = readdirSync(projectDirectory, { withFileTypes: true });
|
|
204
|
+
} catch {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
return jsFileNodes(entries, (name) => name);
|
|
208
|
+
}
|
|
209
|
+
function jsFileNodes(entries, pathFor) {
|
|
210
|
+
const files = entries.filter((entry) => entry.isFile() && isJsSourceModuleFile(entry.name)).toSorted(
|
|
211
|
+
(a, b) => extensionPriority(a.name) - extensionPriority(b.name) || a.name.localeCompare(b.name)
|
|
212
|
+
);
|
|
213
|
+
const byName = /* @__PURE__ */ new Map();
|
|
214
|
+
for (const entry of files) {
|
|
215
|
+
const name = jsModuleName(entry.name);
|
|
216
|
+
if (!byName.has(name)) {
|
|
217
|
+
byName.set(name, { name, path: pathFor(entry.name), purpose: PURPOSE_PLACEHOLDER });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return byName.values().toArray().toSorted(byNodeName);
|
|
221
|
+
}
|
|
222
|
+
function extensionPriority(filename) {
|
|
223
|
+
return JS_SOURCE_EXTENSIONS.findIndex((extension) => filename.endsWith(extension));
|
|
224
|
+
}
|
|
225
|
+
function isJsSourceModuleFile(name) {
|
|
226
|
+
if (name.startsWith(".")) return false;
|
|
227
|
+
if (/\.d\.[mc]?ts$/.test(name)) return false;
|
|
228
|
+
if (/\.config\.[mc]?[jt]sx?$/.test(name)) return false;
|
|
229
|
+
return JS_SOURCE_EXTENSIONS.some((extension) => name.endsWith(extension));
|
|
230
|
+
}
|
|
231
|
+
function jsModuleName(filename) {
|
|
232
|
+
return filename.slice(0, filename.lastIndexOf("."));
|
|
72
233
|
}
|
|
73
234
|
function goLayoutNodes(projectDirectory) {
|
|
74
235
|
return GO_LAYOUT_DIRECTORIES.filter((name) => isDirectory(nodePath.join(projectDirectory, name))).map((name) => ({ name, path: name, purpose: PURPOSE_PLACEHOLDER })).toSorted(byNodeName);
|
|
@@ -161,105 +322,6 @@ function readBoundaryConfig(projectDirectory) {
|
|
|
161
322
|
return "";
|
|
162
323
|
}
|
|
163
324
|
|
|
164
|
-
// src/utils/toml.ts
|
|
165
|
-
function readTomlTableArray(content, table, key) {
|
|
166
|
-
const found = tableArrayBody(content.split(/\r?\n/), table, key);
|
|
167
|
-
if (found === void 0) return void 0;
|
|
168
|
-
const values = found.body.matchAll(/"([^"]*)"|'([^']*)'/g).map((match) => match[1] ?? match[2] ?? "").filter((value) => value.length > 0).toArray();
|
|
169
|
-
return values.length > 0 ? values : void 0;
|
|
170
|
-
}
|
|
171
|
-
function isTomlTableEmptyArray(content, table, key) {
|
|
172
|
-
const found = tableArrayBody(content.split(/\r?\n/), table, key);
|
|
173
|
-
return found !== void 0 && found.closed && found.body.replaceAll(/[\s,]/g, "") === "";
|
|
174
|
-
}
|
|
175
|
-
function hasTomlTable(content, table) {
|
|
176
|
-
return content.split(/\r?\n/).some((line) => tableHeader(stripTomlComment(line).trim()) === table);
|
|
177
|
-
}
|
|
178
|
-
function* linesInTable(content, table) {
|
|
179
|
-
let inTable = false;
|
|
180
|
-
for (const rawLine of content.split(/\r?\n/)) {
|
|
181
|
-
const line = stripTomlComment(rawLine).trim();
|
|
182
|
-
if (line === "") continue;
|
|
183
|
-
const header = tableHeader(line);
|
|
184
|
-
if (header !== void 0) {
|
|
185
|
-
inTable = header === table;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (inTable) yield line;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function hasTomlTableKey(content, table, key) {
|
|
192
|
-
for (const line of linesInTable(content, table)) {
|
|
193
|
-
if (tableValue(line, key) !== void 0) return true;
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
196
|
-
}
|
|
197
|
-
function readTomlTableString(content, table, key) {
|
|
198
|
-
for (const line of linesInTable(content, table)) {
|
|
199
|
-
const value = tableValue(line, key);
|
|
200
|
-
if (value?.startsWith('"')) {
|
|
201
|
-
const end = value.indexOf('"', 1);
|
|
202
|
-
if (end !== -1) return value.slice(1, end);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return void 0;
|
|
206
|
-
}
|
|
207
|
-
function tableArrayBody(lines, table, key) {
|
|
208
|
-
const opened = findTableArrayOpen(lines, table, key);
|
|
209
|
-
if (opened === void 0) return void 0;
|
|
210
|
-
let body = opened.initial;
|
|
211
|
-
for (let index = opened.line; index < lines.length; index += 1) {
|
|
212
|
-
if (index > opened.line) body += `
|
|
213
|
-
${stripTomlComment(lines[index] ?? "")}`;
|
|
214
|
-
const close = indexOfOutsideQuotes(body, "]");
|
|
215
|
-
if (close !== -1) return { body: body.slice(0, close), closed: true };
|
|
216
|
-
}
|
|
217
|
-
return { body, closed: false };
|
|
218
|
-
}
|
|
219
|
-
function findTableArrayOpen(lines, table, key) {
|
|
220
|
-
let inTable = false;
|
|
221
|
-
for (const [index, rawLine] of lines.entries()) {
|
|
222
|
-
const trimmed = stripTomlComment(rawLine).trim();
|
|
223
|
-
const header = tableHeader(trimmed);
|
|
224
|
-
if (header !== void 0) {
|
|
225
|
-
inTable = header === table;
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (!inTable) continue;
|
|
229
|
-
const value = tableValue(trimmed, key);
|
|
230
|
-
if (value?.startsWith("[") === true) return { line: index, initial: value.slice(1) };
|
|
231
|
-
}
|
|
232
|
-
return void 0;
|
|
233
|
-
}
|
|
234
|
-
function indexOfOutsideQuotes(text, target) {
|
|
235
|
-
let quote = "";
|
|
236
|
-
let index = 0;
|
|
237
|
-
while (index < text.length) {
|
|
238
|
-
const character = text[index];
|
|
239
|
-
if (quote === "") {
|
|
240
|
-
if (character === '"' || character === "'") quote = character;
|
|
241
|
-
else if (character === target) return index;
|
|
242
|
-
} else if (character === quote) {
|
|
243
|
-
quote = "";
|
|
244
|
-
}
|
|
245
|
-
index += 1;
|
|
246
|
-
}
|
|
247
|
-
return -1;
|
|
248
|
-
}
|
|
249
|
-
function tableValue(line, key) {
|
|
250
|
-
const equals = line.indexOf("=");
|
|
251
|
-
if (equals === -1 || line.slice(0, equals).trim() !== key) return void 0;
|
|
252
|
-
return line.slice(equals + 1).trim();
|
|
253
|
-
}
|
|
254
|
-
function tableHeader(line) {
|
|
255
|
-
const match = /^\[\[?([^[\]]+)\]\]?\s*$/.exec(line);
|
|
256
|
-
return match?.[1] === void 0 ? void 0 : match[1].trim();
|
|
257
|
-
}
|
|
258
|
-
function stripTomlComment(line) {
|
|
259
|
-
const hash = indexOfOutsideQuotes(line, "#");
|
|
260
|
-
return hash === -1 ? line : line.slice(0, hash);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
325
|
// src/utils/cargo-manifest.ts
|
|
264
326
|
var DEPENDENCY_TABLES = ["dependencies", "dev-dependencies", "build-dependencies"];
|
|
265
327
|
function readCargoWorkspaceMembers(content) {
|
|
@@ -484,13 +546,58 @@ function discoverWorkspaces(projectDirectory) {
|
|
|
484
546
|
patterns: detections.flatMap(
|
|
485
547
|
(detection) => detection.status === "parsed" ? detection.patterns : []
|
|
486
548
|
),
|
|
487
|
-
unreadable: detections.
|
|
488
|
-
(detection) => detection.status === "unreadable" ? [{ manager: detection.manager, config: detection.config }] : []
|
|
489
|
-
)
|
|
549
|
+
unreadable: detections.filter((detection) => detection.status === "unreadable").map((detection) => ({ manager: detection.manager, config: detection.config }))
|
|
490
550
|
};
|
|
491
551
|
}
|
|
492
552
|
function discoverLeafDirectories(projectDirectory) {
|
|
493
|
-
return
|
|
553
|
+
return leafDirectoriesFrom(projectDirectory, discoverWorkspaces(projectDirectory).patterns);
|
|
554
|
+
}
|
|
555
|
+
function leafDirectoriesFrom(projectDirectory, patterns) {
|
|
556
|
+
const workspaceLeaves = resolveLeafDirectories(projectDirectory, patterns);
|
|
557
|
+
const orphanLeaves = discoverOrphanManifestDirectories(projectDirectory);
|
|
558
|
+
return [.../* @__PURE__ */ new Set([...workspaceLeaves, ...orphanLeaves])].toSorted(byString2);
|
|
559
|
+
}
|
|
560
|
+
var ORPHAN_MANIFESTS = [
|
|
561
|
+
{ packs: ["golang", "go"], filename: "go.mod", declaresPackage: (content) => hasGoModule(content) },
|
|
562
|
+
{
|
|
563
|
+
packs: ["python"],
|
|
564
|
+
filename: "pyproject.toml",
|
|
565
|
+
// A real package declares [project] (PEP 621) or [tool.poetry]; a pyproject that
|
|
566
|
+
// only carries [tool.ruff]/[tool.black] tooling config is not a package leaf.
|
|
567
|
+
declaresPackage: (content) => hasTomlTable(content, "project") || hasTomlTable(content, "tool.poetry")
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
packs: ["rust"],
|
|
571
|
+
filename: "Cargo.toml",
|
|
572
|
+
// A member crate declares [package]; a bare [workspace] root is not a leaf.
|
|
573
|
+
declaresPackage: (content) => hasTomlTable(content, "package")
|
|
574
|
+
}
|
|
575
|
+
];
|
|
576
|
+
function discoverOrphanManifestDirectories(projectDirectory) {
|
|
577
|
+
const installed = installedPacks(projectDirectory);
|
|
578
|
+
const directories = /* @__PURE__ */ new Set();
|
|
579
|
+
for (const manifest of ORPHAN_MANIFESTS) {
|
|
580
|
+
if (manifest.packs.every((pack) => !installed.has(pack))) continue;
|
|
581
|
+
for (const dir of orphanDirectoriesFor(projectDirectory, manifest)) directories.add(dir);
|
|
582
|
+
}
|
|
583
|
+
return [...directories];
|
|
584
|
+
}
|
|
585
|
+
function orphanDirectoriesFor(projectDirectory, manifest) {
|
|
586
|
+
return findAllInTree(projectDirectory, manifest.filename).filter((dir) => {
|
|
587
|
+
if (dir === projectDirectory) return false;
|
|
588
|
+
const content = readFileSafe(nodePath4.join(dir, manifest.filename));
|
|
589
|
+
return content !== void 0 && manifest.declaresPackage(content);
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
function hasGoModule(content) {
|
|
593
|
+
return /^module\s+\S+/m.test(content);
|
|
594
|
+
}
|
|
595
|
+
function installedPacks(projectDirectory) {
|
|
596
|
+
const config = readJson(nodePath4.join(projectDirectory, ".safeword", "config.json"));
|
|
597
|
+
if (config === null || typeof config !== "object") return /* @__PURE__ */ new Set();
|
|
598
|
+
const packs = config.installedPacks;
|
|
599
|
+
if (!Array.isArray(packs)) return /* @__PURE__ */ new Set();
|
|
600
|
+
return new Set(packs.filter((pack) => typeof pack === "string"));
|
|
494
601
|
}
|
|
495
602
|
function discoverUnreadableWorkspaces(projectDirectory) {
|
|
496
603
|
return discoverWorkspaces(projectDirectory).unreadable;
|
|
@@ -534,7 +641,7 @@ function hasRecognizedManifest(directory) {
|
|
|
534
641
|
}
|
|
535
642
|
function extractMonorepoModel(projectDirectory) {
|
|
536
643
|
const { patterns, unreadable: unreadableWorkspaces } = discoverWorkspaces(projectDirectory);
|
|
537
|
-
const packages =
|
|
644
|
+
const packages = leafDirectoriesFrom(projectDirectory, patterns).map((dir) => ({
|
|
538
645
|
name: packageName(dir),
|
|
539
646
|
dir,
|
|
540
647
|
purpose: PURPOSE_PLACEHOLDER,
|
|
@@ -552,8 +659,7 @@ function extractMonorepoModel(projectDirectory) {
|
|
|
552
659
|
edges.sort((a, b) => byString2(a.from, b.from) || byString2(a.to, b.to));
|
|
553
660
|
return { packages, edges, unreadableWorkspaces };
|
|
554
661
|
}
|
|
555
|
-
function
|
|
556
|
-
const model = extractMonorepoModel(projectDirectory);
|
|
662
|
+
function monorepoFingerprintOf(projectDirectory, model) {
|
|
557
663
|
const inputs = {
|
|
558
664
|
// Introspection status is part of the root shape: a package gaining or losing
|
|
559
665
|
// a `src/` tree flips its root-index line (marker ↔ described), so the root
|
|
@@ -753,8 +859,8 @@ function leafTarget(packageDirectory) {
|
|
|
753
859
|
);
|
|
754
860
|
}
|
|
755
861
|
function rootIndexTarget(projectDirectory) {
|
|
756
|
-
const fingerprint = monorepoFingerprint(projectDirectory);
|
|
757
862
|
const model = extractMonorepoModel(projectDirectory);
|
|
863
|
+
const fingerprint = monorepoFingerprintOf(projectDirectory, model);
|
|
758
864
|
return {
|
|
759
865
|
path: resolveGeneratedArchitecturePath(projectDirectory),
|
|
760
866
|
fingerprint,
|
|
@@ -902,7 +1008,7 @@ ${items}
|
|
|
902
1008
|
`;
|
|
903
1009
|
}
|
|
904
1010
|
function renderPackageSection(node, stamp) {
|
|
905
|
-
const body = node.introspected ? node.purpose : "> \u26A0 not introspected \u2014 no
|
|
1011
|
+
const body = node.introspected ? node.purpose : "> \u26A0 not introspected \u2014 no source modules to map";
|
|
906
1012
|
return `### ${node.name}
|
|
907
1013
|
|
|
908
1014
|
${RECONCILED_PREFIX} ${stamp} -->
|
|
@@ -911,6 +1017,54 @@ ${body}
|
|
|
911
1017
|
`;
|
|
912
1018
|
}
|
|
913
1019
|
|
|
1020
|
+
// src/utils/architecture-narrative-drift.ts
|
|
1021
|
+
var ADVISORY_PACKAGE_CAP = 6;
|
|
1022
|
+
function architectureNarrativeDriftAdvisoryForProject(projectDirectory) {
|
|
1023
|
+
const narrative = resolveArchitectureNarrative(projectDirectory);
|
|
1024
|
+
const records = listArchitectureRecords(narrative.absolutePath).records;
|
|
1025
|
+
if (records.length === 0) return void 0;
|
|
1026
|
+
const generatedDocument = readFileSafe(resolveGeneratedArchitecturePath(projectDirectory));
|
|
1027
|
+
if (generatedDocument === void 0) return void 0;
|
|
1028
|
+
const narrativeText = records.map((record) => readFileSafe(record) ?? "").join("\n");
|
|
1029
|
+
return narrativeDriftAdvisory(
|
|
1030
|
+
extractRootIndexPackages(generatedDocument),
|
|
1031
|
+
narrativeText,
|
|
1032
|
+
narrative.displayPath
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
function extractRootIndexPackages(generatedDocument) {
|
|
1036
|
+
const packages = [];
|
|
1037
|
+
let isInPackagesSection = false;
|
|
1038
|
+
for (const line of generatedDocument.split(/\r?\n/)) {
|
|
1039
|
+
if (line.startsWith("## ")) {
|
|
1040
|
+
isInPackagesSection = line.slice(3).trim() === "Packages";
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
if (isInPackagesSection && line.startsWith("### ")) {
|
|
1044
|
+
const name = line.slice(4).trim();
|
|
1045
|
+
if (name.length > 0) packages.push(name);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
return packages;
|
|
1049
|
+
}
|
|
1050
|
+
function isMentioned(packageName2, narrativeText) {
|
|
1051
|
+
const tail = packageName2.includes("/") ? packageName2.split("/").at(-1) : void 0;
|
|
1052
|
+
const candidates = tail !== void 0 && tail !== "" ? [packageName2, tail] : [packageName2];
|
|
1053
|
+
return candidates.some((candidate) => nameBoundaryPattern(candidate).test(narrativeText));
|
|
1054
|
+
}
|
|
1055
|
+
function nameBoundaryPattern(name) {
|
|
1056
|
+
const escaped = name.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
1057
|
+
return new RegExp(String.raw`(?<![\w@/.-])${escaped}(?!\w|[.-]\w)`, "i");
|
|
1058
|
+
}
|
|
1059
|
+
function narrativeDriftAdvisory(packageNames, narrativeText, narrativeDisplayPath) {
|
|
1060
|
+
const missing = packageNames.filter((name) => !isMentioned(name, narrativeText));
|
|
1061
|
+
if (missing.length === 0) return void 0;
|
|
1062
|
+
const named = missing.slice(0, ADVISORY_PACKAGE_CAP);
|
|
1063
|
+
const remainder = missing.length - named.length;
|
|
1064
|
+
const listing = remainder > 0 ? `${named.join(", ")} and ${remainder} more` : named.join(", ");
|
|
1065
|
+
return `Architecture narrative (${narrativeDisplayPath}) does not mention ${missing.length} generated package(s): ${listing} \u2014 run /audit to reconcile against architecture.generated.md. (Advisory; nothing is blocked.)`;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
914
1068
|
// src/commands/architecture.ts
|
|
915
1069
|
function architecture(cwd = process.cwd(), options = {}) {
|
|
916
1070
|
if (options.check) {
|
|
@@ -924,8 +1078,13 @@ function architecture(cwd = process.cwd(), options = {}) {
|
|
|
924
1078
|
success(`Architecture state document ${result.action}: ${result.path}`);
|
|
925
1079
|
}
|
|
926
1080
|
warnUnreadableWorkspaces(cwd);
|
|
1081
|
+
warnNarrativeDrift(cwd);
|
|
927
1082
|
return Promise.resolve();
|
|
928
1083
|
}
|
|
1084
|
+
function warnNarrativeDrift(cwd) {
|
|
1085
|
+
const advisory = architectureNarrativeDriftAdvisoryForProject(cwd);
|
|
1086
|
+
if (advisory !== void 0) warn(advisory);
|
|
1087
|
+
}
|
|
929
1088
|
function warnUnreadableWorkspaces(cwd) {
|
|
930
1089
|
for (const workspace of discoverUnreadableWorkspaces(cwd)) {
|
|
931
1090
|
warn(
|
|
@@ -937,17 +1096,19 @@ function architectureStage(cwd) {
|
|
|
937
1096
|
warnUnreadableWorkspaces(cwd);
|
|
938
1097
|
if (!isArchitectureDocumentEnforcementEnabled(cwd)) {
|
|
939
1098
|
success("Architecture doc enforcement is opted out (architectureDocEnforcement: false).");
|
|
1099
|
+
warnNarrativeDrift(cwd);
|
|
940
1100
|
return Promise.resolve();
|
|
941
1101
|
}
|
|
942
1102
|
const changed = selfHealProject(cwd).filter((result) => isWouldChangeAction(result.action));
|
|
943
1103
|
if (changed.length === 0) {
|
|
944
1104
|
success("Architecture docs need no change.");
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1105
|
+
} else {
|
|
1106
|
+
for (const result of changed) {
|
|
1107
|
+
stageDocument(cwd, result);
|
|
1108
|
+
success(`Architecture doc ${result.action} and staged: ${result.path}`);
|
|
1109
|
+
}
|
|
950
1110
|
}
|
|
1111
|
+
warnNarrativeDrift(cwd);
|
|
951
1112
|
return Promise.resolve();
|
|
952
1113
|
}
|
|
953
1114
|
function stageDocument(cwd, result) {
|
|
@@ -959,6 +1120,7 @@ function stageDocument(cwd, result) {
|
|
|
959
1120
|
}
|
|
960
1121
|
function architectureCheck(cwd) {
|
|
961
1122
|
warnUnreadableWorkspaces(cwd);
|
|
1123
|
+
warnNarrativeDrift(cwd);
|
|
962
1124
|
if (!isArchitectureDocumentEnforcementEnabled(cwd)) {
|
|
963
1125
|
success("Architecture doc enforcement is opted out (architectureDocEnforcement: false).");
|
|
964
1126
|
return Promise.resolve();
|
|
@@ -976,4 +1138,4 @@ function architectureCheck(cwd) {
|
|
|
976
1138
|
export {
|
|
977
1139
|
architecture
|
|
978
1140
|
};
|
|
979
|
-
//# sourceMappingURL=architecture-
|
|
1141
|
+
//# sourceMappingURL=architecture-K67T4PSI.js.map
|