archstrict 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.agents/hooks/hooks.json +29 -0
  2. package/.agents/hooks/post-tool-use.mjs +107 -0
  3. package/.agents/hooks/pre-tool-use.mjs +182 -0
  4. package/.agents/mcp/server.mjs +71 -0
  5. package/.agents/plugin.json +19 -0
  6. package/AGENTS.md +81 -0
  7. package/CHANGELOG.md +77 -0
  8. package/README.ja.md +142 -0
  9. package/README.md +143 -2
  10. package/dist/augmentation-cache.js +65 -0
  11. package/dist/check-options.js +40 -0
  12. package/dist/classify.js +148 -0
  13. package/dist/cli.js +243 -0
  14. package/dist/config-pointer.js +251 -0
  15. package/dist/config.js +194 -0
  16. package/dist/edge-cache.js +530 -0
  17. package/dist/gitignore.js +271 -0
  18. package/dist/mcp-server.js +111 -0
  19. package/dist/module-candidates.js +125 -0
  20. package/dist/module-graph.js +2179 -0
  21. package/dist/project-path.js +59 -0
  22. package/dist/report-error.js +13 -0
  23. package/dist/rules/config-meaning.js +143 -0
  24. package/dist/rules/constraints.js +419 -0
  25. package/dist/rules/cycles.js +285 -0
  26. package/dist/rules/deprecated.js +67 -0
  27. package/dist/rules/empty-rule.js +101 -0
  28. package/dist/rules/moves.js +79 -0
  29. package/dist/rules/must-be-empty.js +52 -0
  30. package/dist/rules/public-surface.js +100 -0
  31. package/dist/rules/uncovered.js +75 -0
  32. package/dist/todo-migration.js +112 -0
  33. package/dist/todo-store.js +434 -0
  34. package/dist/type-closure.js +959 -0
  35. package/dist/type-leak.js +590 -0
  36. package/dist/verbs/agents.js +116 -0
  37. package/dist/verbs/check.js +1011 -0
  38. package/dist/verbs/fix.js +170 -0
  39. package/dist/verbs/hotspots.js +261 -0
  40. package/dist/verbs/init.js +538 -0
  41. package/dist/verbs/map-shape.js +78 -0
  42. package/dist/verbs/recommend.js +863 -0
  43. package/dist/verbs/rules.js +188 -0
  44. package/dist/verbs/search.js +109 -0
  45. package/dist/verbs/simulate.js +220 -0
  46. package/dist/verbs/todo.js +180 -0
  47. package/dist/warm-graph.js +82 -0
  48. package/docs/boundary-patterns.md +374 -0
  49. package/docs/calibrated-rules-design.md +124 -0
  50. package/docs/init-singleton-modules.md +133 -0
  51. package/docs/maintenance.md +109 -0
  52. package/docs/releasing.md +58 -0
  53. package/docs/rules-edge-cache.md +50 -0
  54. package/docs/todo-single-file-migration.md +58 -0
  55. package/llms.txt +25 -0
  56. package/package.json +61 -4
  57. package/skills/archstrict/SKILL.md +54 -0
  58. package/skills/archstrict/references/agents-verb.md +39 -0
  59. package/skills/archstrict/references/config.md +116 -0
  60. package/skills/archstrict/references/hook.md +57 -0
  61. package/skills/archstrict/references/path-rules.md +57 -0
  62. package/skills/archstrict/references/patterns.md +915 -0
  63. package/skills/archstrict/references/prove-rules.md +58 -0
  64. package/skills/archstrict/references/rearchitect.md +66 -0
  65. package/skills/archstrict/references/recommend.md +98 -0
  66. package/skills/archstrict/references/rules.md +149 -0
  67. package/skills/archstrict/references/simulate.md +109 -0
@@ -0,0 +1,116 @@
1
+ // Responsibility: install or remove the fixed archstrict instructions in AGENTS.md.
2
+ // Boundary: preserves surrounding bytes and follows links; never reads project configuration.
3
+ import { lstatSync, existsSync, realpathSync, readlinkSync, readFileSync, writeFileSync, renameSync, mkdirSync, rmSync, statSync, chmodSync } from "node:fs";
4
+ import { dirname, isAbsolute, resolve, join } from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ export const ARCHSTRICT_SECTION_START = "<!-- ARCHSTRICT_START -->";
7
+ export const ARCHSTRICT_SECTION_END = "<!-- ARCHSTRICT_END -->";
8
+ export const ARCHSTRICT_INSTRUCTIONS_BLOCK = `${ARCHSTRICT_SECTION_START}
9
+ ## archstrict
10
+
11
+ In projects with an \`archstrict.config.ts\` (module-boundary/architecture linting), run \`archstrict rules <path>\` BEFORE creating a file or adding an import - it reports the module, tags, and constraints that would govern that path, even before it exists. Run \`archstrict check\` after editing to confirm.
12
+
13
+ The full rule reference (every rule's evidence/because/do shape, the config schema, the pre-edit query) is at \`node_modules/archstrict/skills/archstrict/SKILL.md\` when installed via npm - read it before configuring \`archstrict.config.ts\`, or when a violation's \`do:\` text alone isn't enough.
14
+
15
+ If there is no \`archstrict.config.ts\`, skip archstrict entirely - it may not be installed here.
16
+ ${ARCHSTRICT_SECTION_END}`;
17
+ export function resolveWriteTarget(path) {
18
+ let current = path;
19
+ const seen = new Set();
20
+ for (;;) {
21
+ let stat;
22
+ try {
23
+ stat = lstatSync(current);
24
+ }
25
+ catch {
26
+ return current; // A missing target can be created at this path.
27
+ }
28
+ if (!stat.isSymbolicLink()) {
29
+ return existsSync(current) ? realpathSync(current) : current;
30
+ }
31
+ if (seen.has(current)) {
32
+ throw new Error(`agents: symlink cycle detected at '${current}'`);
33
+ }
34
+ seen.add(current);
35
+ const target = readlinkSync(current);
36
+ current = isAbsolute(target) ? target : resolve(dirname(current), target);
37
+ }
38
+ }
39
+ function sectionRange(contents) {
40
+ const start = contents.indexOf(ARCHSTRICT_SECTION_START);
41
+ const endMarker = contents.indexOf(ARCHSTRICT_SECTION_END);
42
+ if (start === -1 && endMarker === -1)
43
+ return undefined;
44
+ // Ambiguous markers cannot define a safe replacement boundary.
45
+ if (start === -1 || endMarker < start ||
46
+ contents.indexOf(ARCHSTRICT_SECTION_START, start + 1) !== -1 ||
47
+ contents.indexOf(ARCHSTRICT_SECTION_END, endMarker + 1) !== -1) {
48
+ throw new Error("agents: incomplete or duplicate ARCHSTRICT markers; file left unchanged");
49
+ }
50
+ return { start, end: endMarker + Buffer.byteLength(ARCHSTRICT_SECTION_END) };
51
+ }
52
+ function removeSection(contents, start, end) {
53
+ // Consume one separator, not the surrounding document's whitespace.
54
+ if (start >= 4 && contents.subarray(start - 4, start).equals(Buffer.from("\r\n\r\n")))
55
+ start -= 4;
56
+ else if (start >= 2 && contents.subarray(start - 2, start).equals(Buffer.from("\n\n")))
57
+ start -= 2;
58
+ if (start === 0 && contents.subarray(end, end + 4).equals(Buffer.from("\r\n\r\n")))
59
+ end += 4;
60
+ else if (start === 0 && contents.subarray(end, end + 2).equals(Buffer.from("\n\n")))
61
+ end += 2;
62
+ else if (contents.subarray(end).equals(Buffer.from("\n")))
63
+ end++;
64
+ else if (contents.subarray(end).equals(Buffer.from("\r\n")))
65
+ end += 2;
66
+ return Buffer.concat([contents.subarray(0, start), contents.subarray(end)]);
67
+ }
68
+ export function writeTarget(path, contents) {
69
+ mkdirSync(dirname(path), { recursive: true });
70
+ const temporary = join(dirname(path), `.archstrict-agents-${randomUUID()}.tmp`);
71
+ const mode = existsSync(path) ? statSync(path).mode & 0o777 : undefined;
72
+ try {
73
+ writeFileSync(temporary, contents, { flag: "wx", mode });
74
+ if (mode !== undefined)
75
+ chmodSync(temporary, mode);
76
+ // Renaming over the resolved target keeps AGENTS.md and intermediate links intact.
77
+ renameSync(temporary, path);
78
+ }
79
+ finally {
80
+ rmSync(temporary, { force: true });
81
+ }
82
+ }
83
+ export function agents(projectRoot, remove = false) {
84
+ const path = resolveWriteTarget(resolve(projectRoot, "AGENTS.md"));
85
+ if (!existsSync(path)) {
86
+ if (remove)
87
+ return { state: "already-absent", path };
88
+ writeTarget(path, Buffer.from(ARCHSTRICT_INSTRUCTIONS_BLOCK + "\n"));
89
+ return { state: "created", path };
90
+ }
91
+ const original = readFileSync(path);
92
+ const section = sectionRange(original);
93
+ if (remove) {
94
+ if (section === undefined)
95
+ return { state: "no-markers", path };
96
+ writeTarget(path, removeSection(original, section.start, section.end));
97
+ return { state: "removed", path };
98
+ }
99
+ const block = Buffer.from(ARCHSTRICT_INSTRUCTIONS_BLOCK);
100
+ const updated = section === undefined
101
+ ? Buffer.concat([original, Buffer.from(original.length > 0 ? "\n\n" : ""), block, Buffer.from("\n")])
102
+ : Buffer.concat([original.subarray(0, section.start), block, original.subarray(section.end)]);
103
+ if (!updated.equals(original))
104
+ writeTarget(path, updated);
105
+ return { state: section === undefined ? "appended" : "replaced", path };
106
+ }
107
+ export function formatAgentsText(result) {
108
+ switch (result.state) {
109
+ case "created": return `created instructions in ${result.path}\n`;
110
+ case "appended": return `appended instructions to ${result.path}\n`;
111
+ case "replaced": return `replaced instructions in ${result.path}\n`;
112
+ case "removed": return `removed instructions from ${result.path}\n`;
113
+ case "already-absent": return `instructions absent at ${result.path}; left unchanged\n`;
114
+ case "no-markers": return `no markers in ${result.path}; left unchanged\n`;
115
+ }
116
+ }