safeword 0.57.0 → 0.59.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-3QIZFW3W.js → architecture-32CAOL4P.js} +386 -165
- package/dist/architecture-32CAOL4P.js.map +1 -0
- package/dist/{check-IV6KC65F.js → check-QZPVKHZF.js} +38 -30
- package/dist/check-QZPVKHZF.js.map +1 -0
- package/dist/{chunk-KIZYVSME.js → chunk-2KELQTLE.js} +32 -4
- package/dist/chunk-2KELQTLE.js.map +1 -0
- package/dist/{chunk-HTDMZQKA.js → chunk-D4UGNHLW.js} +2 -2
- package/dist/{chunk-P2IC575P.js → chunk-HI7ANNQI.js} +2 -2
- package/dist/chunk-HI7ANNQI.js.map +1 -0
- package/dist/{chunk-KC7L2P6V.js → chunk-HKX3Z32Y.js} +151 -14
- package/dist/chunk-HKX3Z32Y.js.map +1 -0
- package/dist/{chunk-YXNI7W5D.js → chunk-HWOJR33A.js} +124 -3
- package/dist/chunk-HWOJR33A.js.map +1 -0
- package/dist/{chunk-HGOG5ZLC.js → chunk-JSTWB4AX.js} +416 -97
- package/dist/chunk-JSTWB4AX.js.map +1 -0
- package/dist/chunk-LRYWFRPD.js +46 -0
- package/dist/chunk-LRYWFRPD.js.map +1 -0
- package/dist/{chunk-O3QF6QHX.js → chunk-N7PFYE7Z.js} +48 -24
- package/dist/chunk-N7PFYE7Z.js.map +1 -0
- package/dist/{chunk-D5H7VBXQ.js → chunk-WUJKWUMR.js} +167 -13
- package/dist/chunk-WUJKWUMR.js.map +1 -0
- package/dist/chunk-XDT3W5MH.js +213 -0
- package/dist/chunk-XDT3W5MH.js.map +1 -0
- package/dist/cli.js +48 -17
- package/dist/cli.js.map +1 -1
- package/dist/{codify-I2FLE35J.js → codify-S4DJVZ4R.js} +4 -4
- package/dist/{diff-55Y2SH4U.js → diff-TFNDFYCX.js} +38 -7
- package/dist/diff-TFNDFYCX.js.map +1 -0
- package/dist/{reset-FZRYTUFF.js → reset-W5GEXNGF.js} +4 -4
- package/dist/self-report-CQLAESVT.js +39 -0
- package/dist/self-report-CQLAESVT.js.map +1 -0
- package/dist/{setup-WKFBBSLJ.js → setup-4KLD7PGB.js} +13 -13
- package/dist/setup-4KLD7PGB.js.map +1 -0
- package/dist/{sync-config-3RYJAKKP.js → sync-config-IUOYVDVQ.js} +3 -4
- package/dist/{sync-learnings-QWFNKMK3.js → sync-learnings-XTHHJDPR.js} +3 -3
- package/dist/{sync-tickets-35ZSEKIE.js → sync-tickets-IQ4AACIH.js} +4 -4
- package/dist/{sync-tracker-YUXD7QKS.js → sync-tracker-2TGUIMAB.js} +4 -4
- package/dist/{test-plan-HWRDWG2X.js → test-plan-6X56C5JZ.js} +48 -10
- package/dist/test-plan-6X56C5JZ.js.map +1 -0
- package/dist/{ticket-new-GXYCW5ML.js → ticket-new-UIOKBXK2.js} +8 -4
- package/dist/{ticket-new-GXYCW5ML.js.map → ticket-new-UIOKBXK2.js.map} +1 -1
- package/dist/{upgrade-MCBH6GTD.js → upgrade-ZWRVFHXV.js} +22 -21
- package/dist/upgrade-ZWRVFHXV.js.map +1 -0
- package/package.json +13 -11
- package/templates/SAFEWORD.md +4 -2
- package/templates/codex/config.toml +12 -3
- package/templates/commands/audit.md +1 -1
- package/templates/commands/verify.md +57 -7
- package/templates/doc-templates/feature-spec-template.md +20 -0
- package/templates/doc-templates/impl-plan-template.md +10 -8
- package/templates/guides/llm-evals-guide.md +485 -0
- package/templates/guides/self-report-filing.md +69 -0
- package/templates/guides/testing-guide.md +145 -20
- package/templates/guides/verification-lanes-guide.md +574 -0
- package/templates/hooks/codex/post-tool-skill-nudge.ts +62 -0
- package/templates/hooks/codex/pre-tool-quality.ts +37 -0
- package/templates/hooks/cursor/after-file-edit.ts +3 -0
- package/templates/hooks/cursor/before-shell-execution.ts +43 -2
- package/templates/hooks/cursor/gate-adapter.ts +274 -24
- package/templates/hooks/cursor/post-tool-quality.ts +2 -16
- package/templates/hooks/cursor/post-tool-skill-nudge.ts +65 -0
- package/templates/hooks/cursor/pre-tool-quality.ts +9 -3
- package/templates/hooks/cursor/stop.ts +24 -1
- package/templates/hooks/lib/active-ticket.ts +25 -0
- package/templates/hooks/lib/architecture-document-nudge.ts +129 -0
- package/templates/hooks/lib/architecture-staged-scope.ts +130 -0
- package/templates/hooks/lib/auto-upgrade-lock.ts +89 -0
- package/templates/hooks/lib/auto-upgrade.ts +417 -0
- package/templates/hooks/lib/branch-staleness.ts +49 -0
- package/templates/hooks/lib/checkbox-transitions.ts +2 -2
- package/templates/hooks/lib/cursor-run-identity.ts +281 -0
- package/templates/hooks/lib/dependency-readiness.ts +83 -0
- package/templates/hooks/lib/done-gate.ts +9 -6
- package/templates/hooks/lib/ledger-git.ts +92 -0
- package/templates/hooks/lib/ledger-validation.ts +31 -24
- package/templates/hooks/lib/namespace-root.ts +1 -1
- package/templates/hooks/lib/quality-state.ts +17 -7
- package/templates/hooks/lib/review-trigger.ts +6 -31
- package/templates/hooks/lib/run-identity.ts +2 -7
- package/templates/hooks/lib/safeword-context.ts +83 -0
- package/templates/hooks/lib/self-report.ts +502 -0
- package/templates/hooks/lib/skill-nudge.ts +264 -0
- package/templates/hooks/post-tool-dependency-readiness.ts +84 -0
- package/templates/hooks/post-tool-lint.ts +3 -0
- package/templates/hooks/post-tool-quality.ts +9 -18
- package/templates/hooks/post-tool-skill-nudge.ts +172 -0
- package/templates/hooks/pre-tool-architecture-stage.ts +9 -0
- package/templates/hooks/pre-tool-quality.ts +25 -4
- package/templates/hooks/pre-tool-stale-main.ts +83 -0
- package/templates/hooks/prompt-questions.ts +2 -1
- package/templates/hooks/record-skill-invocation.ts +41 -8
- package/templates/hooks/session-auto-upgrade.ts +13 -300
- package/templates/hooks/session-codex-start.ts +39 -0
- package/templates/hooks/session-cursor-auto-upgrade.ts +35 -0
- package/templates/hooks/session-dependency-readiness.ts +36 -0
- package/templates/hooks/session-safeword-context.ts +19 -81
- package/templates/hooks/stop-quality.ts +29 -33
- package/templates/hooks/stop-self-report.ts +54 -0
- package/templates/skills/audit/SKILL.md +15 -4
- package/templates/skills/bdd/DISCOVERY.md +24 -8
- package/templates/skills/bdd/DONE.md +2 -0
- package/templates/skills/bdd/SCENARIOS.md +8 -2
- package/templates/skills/bdd/SKILL.md +1 -1
- package/templates/skills/bdd/SPLITTING.md +2 -0
- package/templates/skills/bdd/TDD.md +10 -3
- package/templates/skills/bdd/VERIFY.md +6 -1
- package/templates/skills/figure-it-out/SKILL.md +4 -0
- package/templates/skills/quality-review/SKILL.md +11 -2
- package/templates/skills/refactor/SKILL.md +21 -9
- package/templates/skills/review-spec/SKILL.md +4 -2
- package/templates/skills/self-review/SKILL.md +4 -0
- package/templates/skills/tdd-review/SKILL.md +9 -3
- package/templates/skills/testing/SKILL.md +33 -0
- package/templates/skills/ticket-system/SKILL.md +14 -2
- package/templates/skills/verify/SKILL.md +57 -7
- package/templates/spec-template.md +16 -0
- package/templates/surfaces-template.md +45 -0
- package/dist/architecture-3QIZFW3W.js.map +0 -1
- package/dist/check-IV6KC65F.js.map +0 -1
- package/dist/chunk-D5H7VBXQ.js.map +0 -1
- package/dist/chunk-FJYRWU2V.js +0 -21
- package/dist/chunk-FJYRWU2V.js.map +0 -1
- package/dist/chunk-HGOG5ZLC.js.map +0 -1
- package/dist/chunk-KC7L2P6V.js.map +0 -1
- package/dist/chunk-KIZYVSME.js.map +0 -1
- package/dist/chunk-O3QF6QHX.js.map +0 -1
- package/dist/chunk-P2IC575P.js.map +0 -1
- package/dist/chunk-TTFKJM7Q.js +0 -151
- package/dist/chunk-TTFKJM7Q.js.map +0 -1
- package/dist/chunk-YXNI7W5D.js.map +0 -1
- package/dist/diff-55Y2SH4U.js.map +0 -1
- package/dist/setup-WKFBBSLJ.js.map +0 -1
- package/dist/test-plan-HWRDWG2X.js.map +0 -1
- package/dist/upgrade-MCBH6GTD.js.map +0 -1
- /package/dist/{chunk-HTDMZQKA.js.map → chunk-D4UGNHLW.js.map} +0 -0
- /package/dist/{codify-I2FLE35J.js.map → codify-S4DJVZ4R.js.map} +0 -0
- /package/dist/{reset-FZRYTUFF.js.map → reset-W5GEXNGF.js.map} +0 -0
- /package/dist/{sync-config-3RYJAKKP.js.map → sync-config-IUOYVDVQ.js.map} +0 -0
- /package/dist/{sync-learnings-QWFNKMK3.js.map → sync-learnings-XTHHJDPR.js.map} +0 -0
- /package/dist/{sync-tickets-35ZSEKIE.js.map → sync-tickets-IQ4AACIH.js.map} +0 -0
- /package/dist/{sync-tracker-YUXD7QKS.js.map → sync-tracker-2TGUIMAB.js.map} +0 -0
|
@@ -2,34 +2,32 @@ import {
|
|
|
2
2
|
GENERATED_ARCHITECTURE_FILENAME,
|
|
3
3
|
isArchitectureDocumentEnforcementEnabled,
|
|
4
4
|
resolveGeneratedArchitecturePath
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
detectWorkspaces
|
|
8
|
-
} from "./chunk-TTFKJM7Q.js";
|
|
5
|
+
} from "./chunk-HI7ANNQI.js";
|
|
9
6
|
import {
|
|
10
7
|
error,
|
|
11
|
-
success
|
|
8
|
+
success,
|
|
9
|
+
warn
|
|
12
10
|
} from "./chunk-NWCBLTIE.js";
|
|
13
11
|
import {
|
|
14
12
|
exists,
|
|
15
13
|
isDirectory,
|
|
16
14
|
readFileSafe,
|
|
17
15
|
readJson
|
|
18
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2KELQTLE.js";
|
|
19
17
|
|
|
20
18
|
// src/commands/architecture.ts
|
|
21
19
|
import { execFileSync } from "child_process";
|
|
22
|
-
import
|
|
20
|
+
import nodePath6 from "path";
|
|
23
21
|
import process from "process";
|
|
24
22
|
|
|
25
23
|
// src/utils/architecture-document.ts
|
|
26
24
|
import { mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
27
|
-
import
|
|
25
|
+
import nodePath5 from "path";
|
|
28
26
|
|
|
29
27
|
// src/utils/architecture-fingerprint.ts
|
|
30
28
|
import { createHash } from "crypto";
|
|
31
29
|
import { readdirSync as readdirSync2, readFileSync } from "fs";
|
|
32
|
-
import
|
|
30
|
+
import nodePath3 from "path";
|
|
33
31
|
|
|
34
32
|
// src/utils/architecture-skeleton.ts
|
|
35
33
|
import { readdirSync } from "fs";
|
|
@@ -37,7 +35,19 @@ import nodePath from "path";
|
|
|
37
35
|
var PURPOSE_PLACEHOLDER = "No description yet \u2014 awaiting prose.";
|
|
38
36
|
var GO_LAYOUT_DIRECTORIES = ["cmd", "internal", "pkg"];
|
|
39
37
|
var RUST_ROOT_FILES = /* @__PURE__ */ new Set(["lib.rs", "main.rs"]);
|
|
38
|
+
var PYTHON_EXCLUDED_FILES = /* @__PURE__ */ new Set([
|
|
39
|
+
"setup.py",
|
|
40
|
+
"conftest.py",
|
|
41
|
+
"noxfile.py",
|
|
42
|
+
"__init__.py",
|
|
43
|
+
"__main__.py"
|
|
44
|
+
]);
|
|
45
|
+
var byNodeName = (a, b) => a.name.localeCompare(b.name);
|
|
40
46
|
function extractSkeleton(projectDirectory) {
|
|
47
|
+
if (exists(nodePath.join(projectDirectory, "pyproject.toml"))) {
|
|
48
|
+
const pythonNodes = pythonModuleNodes(projectDirectory);
|
|
49
|
+
if (pythonNodes.length > 0) return { nodes: pythonNodes };
|
|
50
|
+
}
|
|
41
51
|
if (exists(nodePath.join(projectDirectory, "Cargo.toml"))) {
|
|
42
52
|
return { nodes: rustModuleNodes(projectDirectory) };
|
|
43
53
|
}
|
|
@@ -58,10 +68,10 @@ function enumerateModuleDirectories(directory, pathFor) {
|
|
|
58
68
|
} catch {
|
|
59
69
|
return [];
|
|
60
70
|
}
|
|
61
|
-
return entries.filter((entry) => entry.isDirectory()).map((entry) => ({ name: entry.name, path: pathFor(entry.name), purpose: PURPOSE_PLACEHOLDER })).toSorted(
|
|
71
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => ({ name: entry.name, path: pathFor(entry.name), purpose: PURPOSE_PLACEHOLDER })).toSorted(byNodeName);
|
|
62
72
|
}
|
|
63
73
|
function goLayoutNodes(projectDirectory) {
|
|
64
|
-
return GO_LAYOUT_DIRECTORIES.filter((name) => isDirectory(nodePath.join(projectDirectory, name))).map((name) => ({ name, path: name, purpose: PURPOSE_PLACEHOLDER })).toSorted(
|
|
74
|
+
return GO_LAYOUT_DIRECTORIES.filter((name) => isDirectory(nodePath.join(projectDirectory, name))).map((name) => ({ name, path: name, purpose: PURPOSE_PLACEHOLDER })).toSorted(byNodeName);
|
|
65
75
|
}
|
|
66
76
|
function rustModuleNodes(projectDirectory) {
|
|
67
77
|
let entries;
|
|
@@ -89,72 +99,194 @@ function rustModuleNodes(projectDirectory) {
|
|
|
89
99
|
byName.set(name, { name, path: `src/${entry.name}`, purpose: PURPOSE_PLACEHOLDER });
|
|
90
100
|
}
|
|
91
101
|
}
|
|
92
|
-
return byName.values().toArray().toSorted(
|
|
102
|
+
return byName.values().toArray().toSorted(byNodeName);
|
|
103
|
+
}
|
|
104
|
+
function pythonModuleNodes(projectDirectory) {
|
|
105
|
+
const sourceDirectory = nodePath.join(projectDirectory, "src");
|
|
106
|
+
if (isDirectory(sourceDirectory)) {
|
|
107
|
+
return pythonModulesFrom(
|
|
108
|
+
sourceDirectory,
|
|
109
|
+
(name) => `src/${name}`,
|
|
110
|
+
() => true
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return pythonModulesFrom(
|
|
114
|
+
projectDirectory,
|
|
115
|
+
(name) => name,
|
|
116
|
+
(directory) => exists(nodePath.join(directory, "__init__.py"))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function pythonModulesFrom(directory, pathFor, keepPackageDirectory) {
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
123
|
+
} catch {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const byName = /* @__PURE__ */ new Map();
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
if (entry.isDirectory() && keepPackageDirectory(nodePath.join(directory, entry.name))) {
|
|
129
|
+
byName.set(entry.name, {
|
|
130
|
+
name: entry.name,
|
|
131
|
+
path: pathFor(entry.name),
|
|
132
|
+
purpose: PURPOSE_PLACEHOLDER
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
for (const entry of entries) {
|
|
137
|
+
if (entry.isDirectory() || !entry.name.endsWith(".py") || PYTHON_EXCLUDED_FILES.has(entry.name)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const name = entry.name.slice(0, -".py".length);
|
|
141
|
+
if (!byName.has(name)) {
|
|
142
|
+
byName.set(name, { name, path: pathFor(entry.name), purpose: PURPOSE_PLACEHOLDER });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return byName.values().toArray().toSorted(byNodeName);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/utils/boundary-config.ts
|
|
149
|
+
import nodePath2 from "path";
|
|
150
|
+
var DEPENDENCY_CRUISER_CONFIG_NAMES = [
|
|
151
|
+
".dependency-cruiser.cjs",
|
|
152
|
+
".dependency-cruiser.js",
|
|
153
|
+
".dependency-cruiser.mjs",
|
|
154
|
+
".dependency-cruiser.json"
|
|
155
|
+
];
|
|
156
|
+
function readBoundaryConfig(projectDirectory) {
|
|
157
|
+
for (const name of DEPENDENCY_CRUISER_CONFIG_NAMES) {
|
|
158
|
+
const content = readFileSafe(nodePath2.join(projectDirectory, name));
|
|
159
|
+
if (content !== void 0) return content;
|
|
160
|
+
}
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
|
|
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);
|
|
93
261
|
}
|
|
94
262
|
|
|
95
263
|
// src/utils/cargo-manifest.ts
|
|
96
264
|
var DEPENDENCY_TABLES = ["dependencies", "dev-dependencies", "build-dependencies"];
|
|
97
265
|
function readCargoWorkspaceMembers(content) {
|
|
98
|
-
|
|
99
|
-
if (body === void 0) return void 0;
|
|
100
|
-
const globs = body.matchAll(/"([^"]*)"|'([^']*)'/g).map((match) => match[1] ?? match[2] ?? "").filter((glob) => glob.length > 0).toArray();
|
|
101
|
-
return globs.length > 0 ? globs : void 0;
|
|
102
|
-
}
|
|
103
|
-
function workspaceMembersArrayBody(lines) {
|
|
104
|
-
let inWorkspace = false;
|
|
105
|
-
let body;
|
|
106
|
-
for (const rawLine of lines) {
|
|
107
|
-
const line = stripComment(rawLine);
|
|
108
|
-
const trimmed = line.trim();
|
|
109
|
-
if (body === void 0) {
|
|
110
|
-
const header = /^\[\[?([^[\]]+)\]\]?\s*$/.exec(trimmed);
|
|
111
|
-
if (header?.[1] !== void 0) {
|
|
112
|
-
inWorkspace = header[1].trim() === "workspace";
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
const opening = inWorkspace ? /members\s*=\s*\[(.*)$/.exec(trimmed) : void 0;
|
|
116
|
-
if (opening?.[1] === void 0) continue;
|
|
117
|
-
body = opening[1];
|
|
118
|
-
} else {
|
|
119
|
-
body += `
|
|
120
|
-
${line}`;
|
|
121
|
-
}
|
|
122
|
-
const close = body.indexOf("]");
|
|
123
|
-
if (close !== -1) return body.slice(0, close);
|
|
124
|
-
}
|
|
125
|
-
return body;
|
|
266
|
+
return readTomlTableArray(content, "workspace", "members");
|
|
126
267
|
}
|
|
127
268
|
function readCargoPackageName(content) {
|
|
128
|
-
return
|
|
269
|
+
return readTomlTableString(content, "package", "name");
|
|
129
270
|
}
|
|
130
271
|
function readCargoDependencyNames(content) {
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
function scanTables(content) {
|
|
134
|
-
const scan = { packageName: void 0, dependencyNames: /* @__PURE__ */ new Set() };
|
|
272
|
+
const names = /* @__PURE__ */ new Set();
|
|
135
273
|
let currentTable = "";
|
|
136
274
|
for (const rawLine of content.split(/\r?\n/)) {
|
|
137
|
-
const line =
|
|
275
|
+
const line = stripTomlComment(rawLine).trim();
|
|
138
276
|
if (line === "") continue;
|
|
139
277
|
const header = /^\[\[?([^\]]+)\]\]?$/.exec(line);
|
|
140
278
|
if (header?.[1] !== void 0) {
|
|
141
279
|
currentTable = header[1].trim();
|
|
142
280
|
const subTableDependency = dependencySubTableName(currentTable);
|
|
143
|
-
if (subTableDependency !== void 0)
|
|
281
|
+
if (subTableDependency !== void 0) names.add(subTableDependency);
|
|
144
282
|
continue;
|
|
145
283
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
function collectKey(line, currentTable, scan) {
|
|
151
|
-
const key = leadingKey(line);
|
|
152
|
-
if (key === void 0) return;
|
|
153
|
-
if (currentTable === "package" && key === "name") {
|
|
154
|
-
scan.packageName ??= stringValue(line);
|
|
155
|
-
} else if (DEPENDENCY_TABLES.includes(currentTable)) {
|
|
156
|
-
scan.dependencyNames.add(key);
|
|
284
|
+
if (DEPENDENCY_TABLES.includes(currentTable)) {
|
|
285
|
+
const key = leadingKey(line);
|
|
286
|
+
if (key !== void 0) names.add(key);
|
|
287
|
+
}
|
|
157
288
|
}
|
|
289
|
+
return [...names];
|
|
158
290
|
}
|
|
159
291
|
function dependencySubTableName(table) {
|
|
160
292
|
for (const dependencyTable of DEPENDENCY_TABLES) {
|
|
@@ -168,13 +300,6 @@ function leadingKey(line) {
|
|
|
168
300
|
const match = /^("[^"]+"|'[^']+'|[\w-]+)\s*=/.exec(line);
|
|
169
301
|
return match?.[1] === void 0 ? void 0 : unquote(match[1]);
|
|
170
302
|
}
|
|
171
|
-
function stringValue(line) {
|
|
172
|
-
return /=\s*"([^"]*)"/.exec(line)?.[1];
|
|
173
|
-
}
|
|
174
|
-
function stripComment(line) {
|
|
175
|
-
const hash = line.indexOf("#");
|
|
176
|
-
return hash === -1 ? line : line.slice(0, hash);
|
|
177
|
-
}
|
|
178
303
|
function unquote(value) {
|
|
179
304
|
return value.replaceAll(/^["']|["']$/g, "");
|
|
180
305
|
}
|
|
@@ -213,13 +338,22 @@ function dependencySectionNames(manifest) {
|
|
|
213
338
|
return [...names];
|
|
214
339
|
}
|
|
215
340
|
|
|
341
|
+
// src/utils/pyproject-manifest.ts
|
|
342
|
+
function readPyprojectName(content) {
|
|
343
|
+
return readTomlTableString(content, "project", "name");
|
|
344
|
+
}
|
|
345
|
+
function readUvWorkspaceMembers(content) {
|
|
346
|
+
return readTomlTableArray(content, "tool.uv.workspace", "members");
|
|
347
|
+
}
|
|
348
|
+
function readPyprojectDependencies(content) {
|
|
349
|
+
const specifiers = readTomlTableArray(content, "project", "dependencies") ?? [];
|
|
350
|
+
return specifiers.map((specifier) => distributionName(specifier)).filter((name) => name.length > 0);
|
|
351
|
+
}
|
|
352
|
+
function distributionName(specifier) {
|
|
353
|
+
return /^[\w.-]+/.exec(specifier.trim())?.[0] ?? "";
|
|
354
|
+
}
|
|
355
|
+
|
|
216
356
|
// src/utils/architecture-fingerprint.ts
|
|
217
|
-
var DEPENDENCY_CRUISER_CONFIG_NAMES = [
|
|
218
|
-
".dependency-cruiser.cjs",
|
|
219
|
-
".dependency-cruiser.js",
|
|
220
|
-
".dependency-cruiser.mjs",
|
|
221
|
-
".dependency-cruiser.json"
|
|
222
|
-
];
|
|
223
357
|
var SCHEMA_EXTENSIONS = /* @__PURE__ */ new Set([".sql", ".prisma"]);
|
|
224
358
|
var SHAPE_SCAN_EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
225
359
|
".git",
|
|
@@ -246,16 +380,27 @@ function readDependencyNames(projectDirectory) {
|
|
|
246
380
|
const names = new Set(readPackageJsonDependencyNames(projectDirectory));
|
|
247
381
|
for (const goModule of readGoModuleRequires(projectDirectory)) names.add(goModule);
|
|
248
382
|
for (const crate of readCargoDependencies(projectDirectory)) names.add(crate);
|
|
383
|
+
for (const distribution of readPyprojectDependencyNames(projectDirectory))
|
|
384
|
+
names.add(distribution);
|
|
249
385
|
return [...names].toSorted(byString);
|
|
250
386
|
}
|
|
251
387
|
function readPackageJsonDependencyNames(projectDirectory) {
|
|
252
|
-
const manifest =
|
|
388
|
+
const manifest = readJson(nodePath3.join(projectDirectory, "package.json"));
|
|
253
389
|
return manifest === void 0 ? [] : dependencySectionNames(manifest);
|
|
254
390
|
}
|
|
255
391
|
function readCargoDependencies(projectDirectory) {
|
|
256
392
|
try {
|
|
257
393
|
return readCargoDependencyNames(
|
|
258
|
-
readFileSync(
|
|
394
|
+
readFileSync(nodePath3.join(projectDirectory, "Cargo.toml"), "utf8")
|
|
395
|
+
);
|
|
396
|
+
} catch {
|
|
397
|
+
return [];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
function readPyprojectDependencyNames(projectDirectory) {
|
|
401
|
+
try {
|
|
402
|
+
return readPyprojectDependencies(
|
|
403
|
+
readFileSync(nodePath3.join(projectDirectory, "pyproject.toml"), "utf8")
|
|
259
404
|
);
|
|
260
405
|
} catch {
|
|
261
406
|
return [];
|
|
@@ -264,7 +409,7 @@ function readCargoDependencies(projectDirectory) {
|
|
|
264
409
|
function readGoModuleRequires(projectDirectory) {
|
|
265
410
|
let content;
|
|
266
411
|
try {
|
|
267
|
-
content = readFileSync(
|
|
412
|
+
content = readFileSync(nodePath3.join(projectDirectory, "go.mod"), "utf8");
|
|
268
413
|
} catch {
|
|
269
414
|
return [];
|
|
270
415
|
}
|
|
@@ -286,15 +431,6 @@ function collectGoRequireLines(lines, modules) {
|
|
|
286
431
|
if (match?.[1] !== void 0) modules.add(match[1]);
|
|
287
432
|
}
|
|
288
433
|
}
|
|
289
|
-
function readBoundaryConfig(projectDirectory) {
|
|
290
|
-
for (const name of DEPENDENCY_CRUISER_CONFIG_NAMES) {
|
|
291
|
-
try {
|
|
292
|
-
return readFileSync(nodePath2.join(projectDirectory, name), "utf8");
|
|
293
|
-
} catch {
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return "";
|
|
297
|
-
}
|
|
298
434
|
function collectSchemaFiles(projectDirectory) {
|
|
299
435
|
const schemaFiles = [];
|
|
300
436
|
const pending = [projectDirectory];
|
|
@@ -316,42 +452,56 @@ function scanDirectoryForSchema(projectDirectory, directory, schemaFiles, pendin
|
|
|
316
452
|
for (const entry of entries) {
|
|
317
453
|
if (entry.isDirectory()) {
|
|
318
454
|
if (!SHAPE_SCAN_EXCLUDED_DIRECTORIES.has(entry.name)) {
|
|
319
|
-
pending.push(
|
|
455
|
+
pending.push(nodePath3.join(directory, entry.name));
|
|
320
456
|
}
|
|
321
|
-
} else if (SCHEMA_EXTENSIONS.has(
|
|
322
|
-
const absolutePath =
|
|
323
|
-
schemaFiles.push(
|
|
457
|
+
} else if (SCHEMA_EXTENSIONS.has(nodePath3.extname(entry.name))) {
|
|
458
|
+
const absolutePath = nodePath3.join(directory, entry.name);
|
|
459
|
+
schemaFiles.push(nodePath3.relative(projectDirectory, absolutePath).replaceAll("\\", "/"));
|
|
324
460
|
}
|
|
325
461
|
}
|
|
326
462
|
}
|
|
327
|
-
function readJson2(filePath) {
|
|
328
|
-
try {
|
|
329
|
-
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
330
|
-
} catch {
|
|
331
|
-
return void 0;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
463
|
|
|
335
464
|
// src/utils/architecture-monorepo.ts
|
|
336
465
|
import { createHash as createHash2 } from "crypto";
|
|
337
466
|
import { globSync } from "fs";
|
|
338
|
-
import
|
|
339
|
-
var PURPOSE_PLACEHOLDER2 = "No description yet \u2014 awaiting prose.";
|
|
340
|
-
var DEPENDENCY_CRUISER_CONFIG_NAMES2 = [
|
|
341
|
-
".dependency-cruiser.cjs",
|
|
342
|
-
".dependency-cruiser.js",
|
|
343
|
-
".dependency-cruiser.mjs",
|
|
344
|
-
".dependency-cruiser.json"
|
|
345
|
-
];
|
|
467
|
+
import nodePath4 from "path";
|
|
346
468
|
var byString2 = (a, b) => a.localeCompare(b);
|
|
469
|
+
var ABSENT = { status: "absent" };
|
|
470
|
+
var parsed = (patterns) => ({ status: "parsed", patterns });
|
|
471
|
+
var unreadable = (manager, config) => ({
|
|
472
|
+
status: "unreadable",
|
|
473
|
+
manager,
|
|
474
|
+
config
|
|
475
|
+
});
|
|
476
|
+
function discoverWorkspaces(projectDirectory) {
|
|
477
|
+
const detections = [
|
|
478
|
+
detectJsWorkspaces(projectDirectory),
|
|
479
|
+
detectGoWork(projectDirectory),
|
|
480
|
+
detectCargoWorkspace(projectDirectory),
|
|
481
|
+
detectUvWorkspace(projectDirectory)
|
|
482
|
+
];
|
|
483
|
+
return {
|
|
484
|
+
patterns: detections.flatMap(
|
|
485
|
+
(detection) => detection.status === "parsed" ? detection.patterns : []
|
|
486
|
+
),
|
|
487
|
+
unreadable: detections.flatMap(
|
|
488
|
+
(detection) => detection.status === "unreadable" ? [{ manager: detection.manager, config: detection.config }] : []
|
|
489
|
+
)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
347
492
|
function discoverLeafDirectories(projectDirectory) {
|
|
348
|
-
|
|
349
|
-
|
|
493
|
+
return resolveLeafDirectories(projectDirectory, discoverWorkspaces(projectDirectory).patterns);
|
|
494
|
+
}
|
|
495
|
+
function discoverUnreadableWorkspaces(projectDirectory) {
|
|
496
|
+
return discoverWorkspaces(projectDirectory).unreadable;
|
|
497
|
+
}
|
|
498
|
+
function resolveLeafDirectories(projectDirectory, patterns) {
|
|
499
|
+
if (patterns.length === 0) return [];
|
|
350
500
|
const matches = /* @__PURE__ */ new Set();
|
|
351
501
|
for (const pattern of patterns) {
|
|
352
502
|
const globMatches = globSync(pattern, { cwd: projectDirectory });
|
|
353
503
|
for (const match of globMatches) {
|
|
354
|
-
const absolute =
|
|
504
|
+
const absolute = nodePath4.join(projectDirectory, match);
|
|
355
505
|
if (isDirectory(absolute) && hasRecognizedManifest(absolute)) {
|
|
356
506
|
matches.add(absolute);
|
|
357
507
|
}
|
|
@@ -359,14 +509,35 @@ function discoverLeafDirectories(projectDirectory) {
|
|
|
359
509
|
}
|
|
360
510
|
return [...matches].toSorted(byString2);
|
|
361
511
|
}
|
|
512
|
+
function detectJsWorkspaces(projectDirectory) {
|
|
513
|
+
const packageJson = detectPackageJsonWorkspaces(projectDirectory);
|
|
514
|
+
return packageJson.status === "absent" ? detectPnpmWorkspaces(projectDirectory) : packageJson;
|
|
515
|
+
}
|
|
516
|
+
function detectPackageJsonWorkspaces(projectDirectory) {
|
|
517
|
+
const workspaces = readManifest(projectDirectory)?.workspaces;
|
|
518
|
+
if (workspaces === void 0) return ABSENT;
|
|
519
|
+
const list = workspaceList(workspaces);
|
|
520
|
+
if (list === void 0) return unreadable("package.json workspaces", "package.json");
|
|
521
|
+
const patterns = list.filter((entry) => typeof entry === "string");
|
|
522
|
+
return patterns.length > 0 ? parsed(patterns) : ABSENT;
|
|
523
|
+
}
|
|
524
|
+
function workspaceList(workspaces) {
|
|
525
|
+
if (Array.isArray(workspaces)) return workspaces;
|
|
526
|
+
if (isObjectRecord(workspaces) && Array.isArray(workspaces.packages)) return workspaces.packages;
|
|
527
|
+
return void 0;
|
|
528
|
+
}
|
|
529
|
+
function isObjectRecord(value) {
|
|
530
|
+
return typeof value === "object" && value !== null;
|
|
531
|
+
}
|
|
362
532
|
function hasRecognizedManifest(directory) {
|
|
363
|
-
return readJson(
|
|
533
|
+
return readJson(nodePath4.join(directory, "package.json")) !== void 0 || readFileSafe(nodePath4.join(directory, "go.mod")) !== void 0 || readFileSafe(nodePath4.join(directory, "Cargo.toml")) !== void 0 || readFileSafe(nodePath4.join(directory, "pyproject.toml")) !== void 0;
|
|
364
534
|
}
|
|
365
535
|
function extractMonorepoModel(projectDirectory) {
|
|
366
|
-
const
|
|
536
|
+
const { patterns, unreadable: unreadableWorkspaces } = discoverWorkspaces(projectDirectory);
|
|
537
|
+
const packages = resolveLeafDirectories(projectDirectory, patterns).map((dir) => ({
|
|
367
538
|
name: packageName(dir),
|
|
368
539
|
dir,
|
|
369
|
-
purpose:
|
|
540
|
+
purpose: PURPOSE_PLACEHOLDER,
|
|
370
541
|
introspected: extractSkeleton(dir).nodes.length > 0
|
|
371
542
|
})).toSorted((a, b) => byString2(a.name, b.name));
|
|
372
543
|
const names = new Set(packages.map((node) => node.name));
|
|
@@ -379,7 +550,7 @@ function extractMonorepoModel(projectDirectory) {
|
|
|
379
550
|
}
|
|
380
551
|
}
|
|
381
552
|
edges.sort((a, b) => byString2(a.from, b.from) || byString2(a.to, b.to));
|
|
382
|
-
return { packages, edges };
|
|
553
|
+
return { packages, edges, unreadableWorkspaces };
|
|
383
554
|
}
|
|
384
555
|
function monorepoFingerprint(projectDirectory) {
|
|
385
556
|
const model = extractMonorepoModel(projectDirectory);
|
|
@@ -389,47 +560,68 @@ function monorepoFingerprint(projectDirectory) {
|
|
|
389
560
|
// index must re-render — otherwise the "not introspected" marker goes stale.
|
|
390
561
|
packages: model.packages.map((node) => `${node.name}:${node.introspected}`),
|
|
391
562
|
edges: model.edges.map((edge) => `${edge.from}->${edge.to}`),
|
|
392
|
-
boundaryConfig:
|
|
563
|
+
boundaryConfig: readBoundaryConfig(projectDirectory),
|
|
564
|
+
// The unreadable-workspace advisory is root shape too: it must re-render when an
|
|
565
|
+
// unparseable config appears or is fixed. Contributed ONLY when non-empty, so a
|
|
566
|
+
// repo with no unreadable config keeps its existing fingerprint — no churn (UWP4XK).
|
|
567
|
+
...model.unreadableWorkspaces.length > 0 && {
|
|
568
|
+
unreadable: model.unreadableWorkspaces.map((entry) => `${entry.manager}:${entry.config}`)
|
|
569
|
+
}
|
|
393
570
|
};
|
|
394
571
|
return createHash2("sha256").update(JSON.stringify(inputs)).digest("hex");
|
|
395
572
|
}
|
|
396
573
|
function readManifest(packageDirectory) {
|
|
397
|
-
const manifest = readJson(
|
|
574
|
+
const manifest = readJson(nodePath4.join(packageDirectory, "package.json"));
|
|
398
575
|
return manifest !== null && typeof manifest === "object" ? manifest : void 0;
|
|
399
576
|
}
|
|
400
577
|
function packageName(packageDirectory) {
|
|
401
578
|
const name = readManifest(packageDirectory)?.name;
|
|
402
579
|
if (typeof name === "string" && name.length > 0) return name;
|
|
403
|
-
return readGoModuleName(packageDirectory) ?? readCargoCrateName(packageDirectory) ??
|
|
580
|
+
return readGoModuleName(packageDirectory) ?? readCargoCrateName(packageDirectory) ?? readPyprojectCrateName(packageDirectory) ?? nodePath4.basename(packageDirectory);
|
|
404
581
|
}
|
|
405
582
|
function readGoModuleName(packageDirectory) {
|
|
406
|
-
const content = readFileSafe(
|
|
583
|
+
const content = readFileSafe(nodePath4.join(packageDirectory, "go.mod"));
|
|
407
584
|
if (content === void 0) return void 0;
|
|
408
585
|
return /^module\s+(\S+)/m.exec(content)?.[1];
|
|
409
586
|
}
|
|
410
587
|
function readCargoCrateName(packageDirectory) {
|
|
411
|
-
const content = readFileSafe(
|
|
588
|
+
const content = readFileSafe(nodePath4.join(packageDirectory, "Cargo.toml"));
|
|
412
589
|
return content === void 0 ? void 0 : readCargoPackageName(content);
|
|
413
590
|
}
|
|
591
|
+
function readPyprojectCrateName(packageDirectory) {
|
|
592
|
+
const content = readFileSafe(nodePath4.join(packageDirectory, "pyproject.toml"));
|
|
593
|
+
return content === void 0 ? void 0 : readPyprojectName(content);
|
|
594
|
+
}
|
|
595
|
+
function resolveTomlWorkspaceMembers(content, readMembers, table, label, configFile) {
|
|
596
|
+
const members = readMembers(content);
|
|
597
|
+
if (members !== void 0) return parsed(members);
|
|
598
|
+
if (isTomlTableEmptyArray(content, table, "members")) return ABSENT;
|
|
599
|
+
return unreadable(label, configFile);
|
|
600
|
+
}
|
|
601
|
+
function detectUvWorkspace(projectDirectory) {
|
|
602
|
+
const content = readFileSafe(nodePath4.join(projectDirectory, "pyproject.toml"));
|
|
603
|
+
if (content === void 0) return ABSENT;
|
|
604
|
+
if (!hasTomlTable(content, "tool.uv.workspace")) return ABSENT;
|
|
605
|
+
return resolveTomlWorkspaceMembers(
|
|
606
|
+
content,
|
|
607
|
+
readUvWorkspaceMembers,
|
|
608
|
+
"tool.uv.workspace",
|
|
609
|
+
"uv workspace",
|
|
610
|
+
"pyproject.toml"
|
|
611
|
+
);
|
|
612
|
+
}
|
|
414
613
|
function manifestDependencyNames(packageDirectory) {
|
|
415
614
|
const manifest = readManifest(packageDirectory);
|
|
416
615
|
return manifest === void 0 ? [] : dependencySectionNames(manifest);
|
|
417
616
|
}
|
|
418
|
-
function readBoundaryConfig2(projectDirectory) {
|
|
419
|
-
for (const name of DEPENDENCY_CRUISER_CONFIG_NAMES2) {
|
|
420
|
-
const content = readFileSafe(nodePath3.join(projectDirectory, name));
|
|
421
|
-
if (content !== void 0) return content;
|
|
422
|
-
}
|
|
423
|
-
return "";
|
|
424
|
-
}
|
|
425
617
|
function detectPnpmWorkspaces(projectDirectory) {
|
|
426
|
-
const content = readFileSafe(
|
|
427
|
-
if (content === void 0) return
|
|
618
|
+
const content = readFileSafe(nodePath4.join(projectDirectory, "pnpm-workspace.yaml"));
|
|
619
|
+
if (content === void 0) return ABSENT;
|
|
428
620
|
const lines = content.split(/\r?\n/);
|
|
621
|
+
if (lines.every((line) => !line.startsWith("packages:"))) return ABSENT;
|
|
429
622
|
const start = lines.findIndex((line) => /^packages:\s*$/.test(line));
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return globs.length > 0 ? globs : void 0;
|
|
623
|
+
const globs = start === -1 ? [] : collectPnpmGlobs(lines.slice(start + 1));
|
|
624
|
+
return globs.length > 0 ? parsed(globs) : unreadable("pnpm workspaces", "pnpm-workspace.yaml");
|
|
433
625
|
}
|
|
434
626
|
function collectPnpmGlobs(blockLines) {
|
|
435
627
|
const globs = [];
|
|
@@ -444,13 +636,14 @@ function collectPnpmGlobs(blockLines) {
|
|
|
444
636
|
return globs;
|
|
445
637
|
}
|
|
446
638
|
function detectGoWork(projectDirectory) {
|
|
447
|
-
const content = readFileSafe(
|
|
448
|
-
if (content === void 0) return
|
|
639
|
+
const content = readFileSafe(nodePath4.join(projectDirectory, "go.work"));
|
|
640
|
+
if (content === void 0) return ABSENT;
|
|
449
641
|
const lines = content.split(/\r?\n/);
|
|
450
642
|
const directories = [];
|
|
451
643
|
collectGoWorkBlock(lines, directories);
|
|
452
644
|
collectGoWorkSingleLines(lines, directories);
|
|
453
|
-
|
|
645
|
+
if (directories.length > 0) return parsed(directories);
|
|
646
|
+
return lines.some((line) => /^\s*use\b/.test(line)) ? unreadable("go.work", "go.work") : ABSENT;
|
|
454
647
|
}
|
|
455
648
|
function collectGoWorkBlock(lines, directories) {
|
|
456
649
|
for (const entry of readDelimitedBlock(lines, /^use\s*\(\s*$/)) {
|
|
@@ -474,8 +667,17 @@ function normalizeUseTarget(raw) {
|
|
|
474
667
|
return unquoted.replace(/^\.\//, "");
|
|
475
668
|
}
|
|
476
669
|
function detectCargoWorkspace(projectDirectory) {
|
|
477
|
-
const content = readFileSafe(
|
|
478
|
-
|
|
670
|
+
const content = readFileSafe(nodePath4.join(projectDirectory, "Cargo.toml"));
|
|
671
|
+
if (content === void 0) return ABSENT;
|
|
672
|
+
if (!hasTomlTable(content, "workspace")) return ABSENT;
|
|
673
|
+
if (!hasTomlTableKey(content, "workspace", "members")) return ABSENT;
|
|
674
|
+
return resolveTomlWorkspaceMembers(
|
|
675
|
+
content,
|
|
676
|
+
readCargoWorkspaceMembers,
|
|
677
|
+
"workspace",
|
|
678
|
+
"Cargo [workspace]",
|
|
679
|
+
"Cargo.toml"
|
|
680
|
+
);
|
|
479
681
|
}
|
|
480
682
|
|
|
481
683
|
// src/utils/architecture-reconcile.ts
|
|
@@ -521,7 +723,7 @@ function healTarget(target) {
|
|
|
521
723
|
const existing = readExisting(target.path);
|
|
522
724
|
const action = decideAction(existing, target.fingerprint, target.hasContent);
|
|
523
725
|
if (isWouldChangeAction(action)) {
|
|
524
|
-
mkdirSync(
|
|
726
|
+
mkdirSync(nodePath5.dirname(target.path), { recursive: true });
|
|
525
727
|
const priorStamps = existing === void 0 ? /* @__PURE__ */ new Map() : parseSectionStamps(existing);
|
|
526
728
|
const priorProse = existing === void 0 ? /* @__PURE__ */ new Map() : parseSectionProse(existing);
|
|
527
729
|
writeFileSync(target.path, target.render(priorStamps, priorProse));
|
|
@@ -531,25 +733,24 @@ function healTarget(target) {
|
|
|
531
733
|
function planTarget(target) {
|
|
532
734
|
return decideAction(readExisting(target.path), target.fingerprint, target.hasContent);
|
|
533
735
|
}
|
|
534
|
-
function
|
|
535
|
-
const fingerprint = shapeFingerprint(
|
|
536
|
-
const nodes = extractSkeleton(
|
|
736
|
+
function skeletonTarget(directory, path) {
|
|
737
|
+
const fingerprint = shapeFingerprint(directory);
|
|
738
|
+
const nodes = extractSkeleton(directory).nodes;
|
|
537
739
|
return {
|
|
538
|
-
path
|
|
740
|
+
path,
|
|
539
741
|
fingerprint,
|
|
540
742
|
hasContent: nodes.length > 0,
|
|
541
743
|
render: (priorStamps, priorProse) => renderDocument(nodes, fingerprint, priorStamps, priorProse)
|
|
542
744
|
};
|
|
543
745
|
}
|
|
746
|
+
function singleRepoTarget(projectDirectory) {
|
|
747
|
+
return skeletonTarget(projectDirectory, resolveGeneratedArchitecturePath(projectDirectory));
|
|
748
|
+
}
|
|
544
749
|
function leafTarget(packageDirectory) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
fingerprint,
|
|
550
|
-
hasContent: nodes.length > 0,
|
|
551
|
-
render: (priorStamps, priorProse) => renderDocument(nodes, fingerprint, priorStamps, priorProse)
|
|
552
|
-
};
|
|
750
|
+
return skeletonTarget(
|
|
751
|
+
packageDirectory,
|
|
752
|
+
nodePath5.join(packageDirectory, GENERATED_ARCHITECTURE_FILENAME)
|
|
753
|
+
);
|
|
553
754
|
}
|
|
554
755
|
function rootIndexTarget(projectDirectory) {
|
|
555
756
|
const fingerprint = monorepoFingerprint(projectDirectory);
|
|
@@ -557,13 +758,18 @@ function rootIndexTarget(projectDirectory) {
|
|
|
557
758
|
return {
|
|
558
759
|
path: resolveGeneratedArchitecturePath(projectDirectory),
|
|
559
760
|
fingerprint,
|
|
560
|
-
|
|
761
|
+
// An unreadable workspace is content too: a root index that exists only to carry the
|
|
762
|
+
// "config unreadable" advisory is still worth writing — silence would read as "no
|
|
763
|
+
// monorepo here" when in fact one is present but unreadable (UWP4XK).
|
|
764
|
+
hasContent: model.packages.length > 0 || model.unreadableWorkspaces.length > 0,
|
|
561
765
|
render: () => renderRootIndex(model, fingerprint)
|
|
562
766
|
};
|
|
563
767
|
}
|
|
564
768
|
function projectTargets(projectDirectory) {
|
|
565
769
|
const leaves = discoverLeafDirectories(projectDirectory);
|
|
566
|
-
if (leaves.length === 0
|
|
770
|
+
if (leaves.length === 0 && discoverUnreadableWorkspaces(projectDirectory).length === 0) {
|
|
771
|
+
return [singleRepoTarget(projectDirectory)];
|
|
772
|
+
}
|
|
567
773
|
return [rootIndexTarget(projectDirectory), ...leaves.map((leaf) => leafTarget(leaf))];
|
|
568
774
|
}
|
|
569
775
|
function selfHealProject(projectDirectory) {
|
|
@@ -630,6 +836,16 @@ function accumulateProseLine(line, inProse, buffer) {
|
|
|
630
836
|
buffer.push(line);
|
|
631
837
|
return true;
|
|
632
838
|
}
|
|
839
|
+
function architectureFrontmatter(fingerprint) {
|
|
840
|
+
return `---
|
|
841
|
+
${GENERATOR_KEY}: ${GENERATOR_VALUE}
|
|
842
|
+
${FINGERPRINT_KEY}: ${fingerprint}
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
# Architecture
|
|
846
|
+
|
|
847
|
+
`;
|
|
848
|
+
}
|
|
633
849
|
function renderDocument(nodes, fingerprint, priorStamps, priorProse) {
|
|
634
850
|
const verdicts = reconcileSections({
|
|
635
851
|
priorStamps: Object.fromEntries(priorStamps),
|
|
@@ -643,14 +859,7 @@ function renderDocument(nodes, fingerprint, priorStamps, priorProse) {
|
|
|
643
859
|
const prose = priorProse.get(verdict.node) ?? node?.purpose ?? "";
|
|
644
860
|
return node === void 0 ? renderOrphanSection(verdict.node) : renderSection(node, stamp, verdict.status, prose);
|
|
645
861
|
}).join("\n");
|
|
646
|
-
return
|
|
647
|
-
${GENERATOR_KEY}: ${GENERATOR_VALUE}
|
|
648
|
-
${FINGERPRINT_KEY}: ${fingerprint}
|
|
649
|
-
---
|
|
650
|
-
|
|
651
|
-
# Architecture
|
|
652
|
-
|
|
653
|
-
## Modules
|
|
862
|
+
return `${architectureFrontmatter(fingerprint)}## Modules
|
|
654
863
|
|
|
655
864
|
${sections}`;
|
|
656
865
|
}
|
|
@@ -676,19 +885,21 @@ function renderRootIndex(model, fingerprint) {
|
|
|
676
885
|
const edgeLines = model.edges.map((edge) => `- \`${edge.from}\` \u2192 \`${edge.to}\``).join("\n");
|
|
677
886
|
const dependencies = model.edges.length === 0 ? "_No inter-package dependencies._\n" : `${edgeLines}
|
|
678
887
|
`;
|
|
679
|
-
return
|
|
680
|
-
${GENERATOR_KEY}: ${GENERATOR_VALUE}
|
|
681
|
-
${FINGERPRINT_KEY}: ${fingerprint}
|
|
682
|
-
---
|
|
683
|
-
|
|
684
|
-
# Architecture
|
|
685
|
-
|
|
686
|
-
## Packages
|
|
888
|
+
return `${architectureFrontmatter(fingerprint)}## Packages
|
|
687
889
|
|
|
688
890
|
${sections}
|
|
689
891
|
## Dependencies
|
|
690
892
|
|
|
691
|
-
${dependencies}`;
|
|
893
|
+
${dependencies}${renderCoverageGaps(model.unreadableWorkspaces)}`;
|
|
894
|
+
}
|
|
895
|
+
function renderCoverageGaps(unreadable2) {
|
|
896
|
+
if (unreadable2.length === 0) return "";
|
|
897
|
+
const items = unreadable2.map((entry) => `> - \`${entry.config}\` (${entry.manager})`).join("\n");
|
|
898
|
+
return `## Coverage gaps
|
|
899
|
+
|
|
900
|
+
> \u26A0 not introspected \u2014 workspace config unreadable. A present workspace manager's member list could not be parsed, so its packages may be missing above. Fix the config and re-run \`safeword architecture\`:
|
|
901
|
+
${items}
|
|
902
|
+
`;
|
|
692
903
|
}
|
|
693
904
|
function renderPackageSection(node, stamp) {
|
|
694
905
|
const body = node.introspected ? node.purpose : "> \u26A0 not introspected \u2014 no recognized source layout";
|
|
@@ -712,9 +923,18 @@ function architecture(cwd = process.cwd(), options = {}) {
|
|
|
712
923
|
for (const result of results) {
|
|
713
924
|
success(`Architecture state document ${result.action}: ${result.path}`);
|
|
714
925
|
}
|
|
926
|
+
warnUnreadableWorkspaces(cwd);
|
|
715
927
|
return Promise.resolve();
|
|
716
928
|
}
|
|
929
|
+
function warnUnreadableWorkspaces(cwd) {
|
|
930
|
+
for (const workspace of discoverUnreadableWorkspaces(cwd)) {
|
|
931
|
+
warn(
|
|
932
|
+
`Workspace config present but unreadable: ${workspace.config} (${workspace.manager}). Its packages may be missing from the architecture doc \u2014 fix the config and re-run \`safeword architecture\`. (Advisory; nothing is blocked.)`
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
717
936
|
function architectureStage(cwd) {
|
|
937
|
+
warnUnreadableWorkspaces(cwd);
|
|
718
938
|
if (!isArchitectureDocumentEnforcementEnabled(cwd)) {
|
|
719
939
|
success("Architecture doc enforcement is opted out (architectureDocEnforcement: false).");
|
|
720
940
|
return Promise.resolve();
|
|
@@ -732,12 +952,13 @@ function architectureStage(cwd) {
|
|
|
732
952
|
}
|
|
733
953
|
function stageDocument(cwd, result) {
|
|
734
954
|
try {
|
|
735
|
-
const relativePath =
|
|
955
|
+
const relativePath = nodePath6.relative(cwd, result.path);
|
|
736
956
|
execFileSync("git", ["add", "--", relativePath], { cwd, stdio: "ignore" });
|
|
737
957
|
} catch {
|
|
738
958
|
}
|
|
739
959
|
}
|
|
740
960
|
function architectureCheck(cwd) {
|
|
961
|
+
warnUnreadableWorkspaces(cwd);
|
|
741
962
|
if (!isArchitectureDocumentEnforcementEnabled(cwd)) {
|
|
742
963
|
success("Architecture doc enforcement is opted out (architectureDocEnforcement: false).");
|
|
743
964
|
return Promise.resolve();
|
|
@@ -755,4 +976,4 @@ function architectureCheck(cwd) {
|
|
|
755
976
|
export {
|
|
756
977
|
architecture
|
|
757
978
|
};
|
|
758
|
-
//# sourceMappingURL=architecture-
|
|
979
|
+
//# sourceMappingURL=architecture-32CAOL4P.js.map
|