@astryxdesign/cli 0.7.2 → 0.7.3-canary.06872b4

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 (212) hide show
  1. package/README.md +99 -66
  2. package/api/build/_adapter.d.mts +58 -0
  3. package/api/build/_adapter.mjs +115 -3
  4. package/api/build/blocks/blocks.d.mts +42 -0
  5. package/api/build/blocks/blocks.mjs +169 -0
  6. package/api/build/blocks/blocks.test.mjs +108 -0
  7. package/api/build/blocks/blocks.weights.json +1 -0
  8. package/api/build/build.mjs +25 -6
  9. package/api/build/kit/answer.d.mts +14 -0
  10. package/api/build/kit/answer.mjs +85 -0
  11. package/api/build/kit/answer.test.mjs +38 -0
  12. package/api/build/kit/kind.d.mts +25 -0
  13. package/api/build/kit/kind.mjs +170 -0
  14. package/api/build/kit/kit.d.mts +2 -4
  15. package/api/build/kit/kit.mjs +8 -398
  16. package/api/build/kit/rank.d.mts +3 -32
  17. package/api/build/kit/rank.mjs +25 -342
  18. package/api/build/kit/rank.test.mjs +2 -1
  19. package/api/build/kit/signals.d.mts +60 -0
  20. package/api/build/kit/signals.mjs +284 -0
  21. package/api/build/kit/start.d.mts +32 -0
  22. package/api/build/kit/start.mjs +163 -0
  23. package/api/build/kit/words.d.mts +72 -0
  24. package/api/build/kit/words.mjs +202 -0
  25. package/api/build/tree.d.mts +20 -0
  26. package/api/build/tree.mjs +52 -0
  27. package/api/build/tree.test.mjs +177 -0
  28. package/api/build/tree.weights.json +1 -0
  29. package/api/component/component.mjs +16 -2
  30. package/api/component/component.test.mjs +15 -0
  31. package/api/discover/_adapter.mjs +36 -9
  32. package/api/discover/_adapter.test.mjs +42 -0
  33. package/api/docs/_adapter.mjs +17 -5
  34. package/api/docs/docs.test.mjs +7 -1
  35. package/api/doctor/doctor.mjs +86 -12
  36. package/api/doctor/doctor.test.mjs +28 -3
  37. package/api/init/init.doc.mjs +20 -6
  38. package/api/init/init.test.mjs +18 -2
  39. package/api/init/init.type.d.mts +3 -3
  40. package/api/init/init.type.mjs +3 -3
  41. package/api/init/remove/remove.mjs +17 -4
  42. package/api/init/remove/remove.test.mjs +114 -2
  43. package/api/init/run/run.mjs +69 -6
  44. package/api/integration/authoring-checks.mjs +83 -9
  45. package/api/integration/pack-check.mjs +123 -4
  46. package/api/integration/pack-check.test.mjs +87 -0
  47. package/api/integration/validate-integration-case-disk.test.mjs +111 -0
  48. package/api/integration/validate-integration-gaps.test.mjs +1964 -0
  49. package/api/integration/validate-integration.d.mts +11 -2
  50. package/api/integration/validate-integration.mjs +226 -32
  51. package/api/integration/validate-integration.type.d.mts +58 -1
  52. package/api/integration/validate-integration.type.mjs +45 -2
  53. package/api/integration/validateIntegration.doc.mjs +1 -1
  54. package/api/search/ORDERING.md +28 -0
  55. package/api/search/_adapter.d.mts +66 -2
  56. package/api/search/_adapter.mjs +118 -10
  57. package/api/search/_candidate-score.d.mts +40 -0
  58. package/api/search/_candidate-score.mjs +238 -0
  59. package/api/search/_cards.json +6098 -0
  60. package/api/search/_compound.d.mts +50 -0
  61. package/api/search/_compound.mjs +170 -0
  62. package/api/search/_doc-searcher.d.mts +4 -0
  63. package/api/search/_doc-searcher.mjs +23 -0
  64. package/api/search/_doc-searcher.test.mjs +36 -0
  65. package/api/search/_doc-searcher.weights.json +1 -0
  66. package/api/search/_doc-text.d.mts +30 -0
  67. package/api/search/_doc-text.mjs +87 -0
  68. package/api/search/_entries.d.mts +52 -0
  69. package/api/search/_entries.mjs +158 -0
  70. package/api/search/_features.d.mts +35 -0
  71. package/api/search/_features.mjs +155 -0
  72. package/api/search/_gather-components.d.mts +26 -0
  73. package/api/search/_gather-components.mjs +284 -0
  74. package/api/search/_gather-docs.d.mts +20 -0
  75. package/api/search/_gather-docs.mjs +254 -0
  76. package/api/search/_gather-hooks.d.mts +12 -0
  77. package/api/search/_gather-hooks.mjs +52 -0
  78. package/api/search/_gather-templates.d.mts +25 -0
  79. package/api/search/_gather-templates.mjs +107 -0
  80. package/api/search/_lexical.d.mts +135 -0
  81. package/api/search/_lexical.mjs +351 -0
  82. package/api/search/_main-searcher.d.mts +19 -0
  83. package/api/search/_main-searcher.mjs +115 -0
  84. package/api/search/_main-searcher.test.mjs +106 -0
  85. package/api/search/_main-searcher.weights.json +1 -0
  86. package/api/search/_module-doc.d.mts +32 -0
  87. package/api/search/_module-doc.mjs +78 -0
  88. package/api/search/_order.d.mts +32 -0
  89. package/api/search/_order.mjs +162 -0
  90. package/api/search/_order.test.mjs +72 -0
  91. package/api/search/_priority.d.mts +50 -0
  92. package/api/search/_priority.mjs +139 -0
  93. package/api/search/_query-words.d.mts +25 -0
  94. package/api/search/_query-words.mjs +92 -0
  95. package/api/search/_query.d.mts +65 -0
  96. package/api/search/_query.mjs +176 -0
  97. package/api/search/_result.d.mts +97 -0
  98. package/api/search/_result.mjs +150 -0
  99. package/api/search/_score.d.mts +60 -0
  100. package/api/search/_score.mjs +283 -0
  101. package/api/search/_synonyms.d.mts +21 -0
  102. package/api/search/_synonyms.mjs +67 -0
  103. package/api/search/_template-searcher.d.mts +4 -0
  104. package/api/search/_template-searcher.mjs +23 -0
  105. package/api/search/_template-searcher.test.mjs +40 -0
  106. package/api/search/_template-searcher.weights.json +1 -0
  107. package/api/search/_titles.d.mts +33 -0
  108. package/api/search/_titles.mjs +65 -0
  109. package/api/search/_words.d.mts +64 -0
  110. package/api/search/_words.mjs +157 -0
  111. package/api/search/search.d.mts +22 -305
  112. package/api/search/search.doc.mjs +7 -5
  113. package/api/search/search.mjs +53 -2483
  114. package/api/search/search.type.d.mts +6 -1
  115. package/api/search/search.type.mjs +6 -1
  116. package/api/search/tree.d.mts +15 -0
  117. package/api/search/tree.mjs +40 -0
  118. package/api/search/tree.test.mjs +66 -0
  119. package/api/search/tree.weights.json +1 -0
  120. package/api/theme/build/build.packed-old-core.test.mjs +1 -0
  121. package/api/upgrade/_adapter.d.mts +57 -9
  122. package/api/upgrade/_adapter.mjs +198 -28
  123. package/api/upgrade/registry/registry.d.mts +10 -4
  124. package/api/upgrade/registry/registry.mjs +33 -16
  125. package/api/upgrade/registry/registry.test.mjs +85 -0
  126. package/api/upgrade/run/run.d.mts +4 -2
  127. package/api/upgrade/run/run.mjs +13 -2
  128. package/api/upgrade/run/run.test.mjs +417 -3
  129. package/api/upgrade/upgrade.doc.mjs +10 -5
  130. package/api/upgrade/upgrade.mjs +13 -2
  131. package/api/upgrade/upgrade.test.mjs +194 -0
  132. package/api/upgrade/upgrade.type.d.mts +5 -3
  133. package/api/upgrade/upgrade.type.mjs +8 -6
  134. package/assets/codemods/transforms/v0.7.0/__tests__/validate-data-token-ownership.test.mjs +11 -0
  135. package/assets/codemods/transforms/v0.7.0/validate-data-token-ownership.mjs +3 -3
  136. package/assets/docs/is-my-code-good.doc.mjs +287 -0
  137. package/assets/docs/tree/api.doc.mjs +6 -0
  138. package/assets/docs/tree/host-entry.doc.mjs +128 -0
  139. package/assets/docs/tree/template-grading-rubric.doc.mjs +16 -37
  140. package/assets/docs/tree/tokens-data.doc.mjs +2 -2
  141. package/assets/docs/working-with-ai.doc.mjs +6 -2
  142. package/assets/templates/themes/butter/butterTheme.ts +4 -2
  143. package/assets/templates/themes/stone/stoneTheme.ts +4 -2
  144. package/assets/templates/themes/y2k/y2kTheme.ts +4 -2
  145. package/authoring/debug/parse.d.mts +1 -1
  146. package/clients/cli/bin/astryx.mjs +7 -78
  147. package/clients/cli/commands/doctor.mjs +30 -0
  148. package/clients/cli/commands/doctor.test.mjs +65 -0
  149. package/clients/cli/commands/init.behavior.test.mjs +230 -1
  150. package/clients/cli/commands/init.doc.mjs +28 -9
  151. package/clients/cli/commands/init.flag-help.test.mjs +1 -0
  152. package/clients/cli/commands/init.mjs +2 -2
  153. package/clients/cli/commands/outside-project.test.mjs +2 -1
  154. package/clients/cli/commands/setup-nudge.test.mjs +61 -0
  155. package/clients/cli/commands/upgrade.doc.mjs +4 -2
  156. package/clients/cli/commands/upgrade.mjs +9 -2
  157. package/clients/cli/commands/upgrade.subfolder-path.test.mjs +97 -0
  158. package/clients/cli/index.mjs +19 -1
  159. package/clients/cli/run.mjs +108 -0
  160. package/foundation/agent-docs/agent-doc-state.d.mts +20 -7
  161. package/foundation/agent-docs/agent-doc-state.mjs +35 -8
  162. package/foundation/agent-docs/agent-docs.d.mts +100 -12
  163. package/foundation/agent-docs/agent-docs.mjs +1010 -99
  164. package/foundation/agent-docs/agent-docs.path-safety.test.mjs +1239 -17
  165. package/foundation/agent-docs/agent-docs.test.mjs +678 -35
  166. package/foundation/config/project.mjs +20 -22
  167. package/foundation/config/project.test.mjs +56 -0
  168. package/foundation/discovery/component-discovery.d.mts +37 -0
  169. package/foundation/discovery/component-discovery.mjs +80 -5
  170. package/foundation/discovery/component-loader.d.mts +1 -1
  171. package/foundation/discovery/docs-discovery.mjs +14 -1
  172. package/foundation/discovery/module-exports.d.mts +41 -0
  173. package/foundation/discovery/module-exports.mjs +253 -0
  174. package/foundation/discovery/template-adapter.mjs +18 -1
  175. package/foundation/discovery/template-needs.mjs +1 -44
  176. package/foundation/discovery/theme-discovery.mjs +147 -3
  177. package/foundation/doc-compiler/doc-loads.test.mjs +8 -0
  178. package/foundation/doc-compiler/read.mjs +59 -1
  179. package/foundation/doc-compiler/read.test.mjs +11 -2
  180. package/foundation/fs/paths.d.mts +70 -8
  181. package/foundation/fs/paths.mjs +556 -38
  182. package/foundation/fs/paths.test.mjs +430 -54
  183. package/foundation/integrations/autolink.d.mts +2 -1
  184. package/foundation/integrations/autolink.mjs +4 -13
  185. package/foundation/integrations/computer-integrations.d.mts +6 -3
  186. package/foundation/integrations/computer-integrations.mjs +38 -9
  187. package/foundation/integrations/contribution-inventory.d.mts +15 -20
  188. package/foundation/integrations/contribution-inventory.mjs +41 -5
  189. package/foundation/integrations/integrations.d.mts +27 -1
  190. package/foundation/integrations/integrations.mjs +61 -2
  191. package/foundation/integrations/validate-contributions.d.mts +115 -0
  192. package/foundation/integrations/validate-contributions.mjs +1168 -13
  193. package/foundation/release/handoff.d.mts +23 -4
  194. package/foundation/release/handoff.mjs +175 -8
  195. package/foundation/release/run-context.mjs +14 -0
  196. package/foundation/response/response-types.doc.mjs +1 -1
  197. package/foundation/route/route.d.mts +285 -0
  198. package/foundation/route/route.mjs +324 -0
  199. package/foundation/route/route.test.mjs +153 -0
  200. package/foundation/runtime/hosted-runtime.test.mjs +403 -0
  201. package/foundation/runtime/locked-versions.d.mts +32 -0
  202. package/foundation/runtime/locked-versions.mjs +65 -0
  203. package/foundation/runtime/package-root.d.mts +91 -0
  204. package/foundation/runtime/package-root.mjs +174 -0
  205. package/foundation/runtime/package-set.d.mts +62 -0
  206. package/foundation/runtime/package-set.mjs +130 -0
  207. package/foundation/runtime/package-set.test.mjs +432 -0
  208. package/host/door-conformance.test.mjs +274 -0
  209. package/host/index.d.ts +116 -0
  210. package/host/index.mjs +295 -0
  211. package/host/index.test.mjs +302 -0
  212. package/package.json +14 -9
@@ -0,0 +1,108 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file One run of the CLI, after the Node version gate: choose who answers
5
+ * the folder, hand the run off when that is another release (spec AST-081
6
+ * FR2, FR9), then dispatch the command.
7
+ *
8
+ * @input The arguments after `astryx`, read from `process.argv`; the CLI
9
+ * package root; and, for a run a programmatic host started, the reason the
10
+ * host declined the project's own CLI.
11
+ * @output The command's effects. The run owns the process: its exit status is
12
+ * `process.exitCode`, unless a hand-off or a failure exits the process.
13
+ * @position clients/cli — called by `bin/astryx.mjs` and by the host entry
14
+ * (`host/index.mjs`).
15
+ */
16
+
17
+ /**
18
+ * Run the CLI once in this process.
19
+ *
20
+ * Every import here is dynamic: modules that load `styleText` must load only
21
+ * after the bin's Node version gate, and the release choice must run before
22
+ * the command modules load, so a run handed to another release loads none of
23
+ * them.
24
+ *
25
+ * @param {{cliRoot: string, argv: string[], declinedProjectCli?: string}} input
26
+ * @returns {Promise<import('../../host/index.js').HostedCliResult | undefined>}
27
+ * resolves when the command has finished; with another release's result
28
+ * when, in a hosted run, that release's host entry answered
29
+ */
30
+ export async function runCli({cliRoot, argv, declinedProjectCli}) {
31
+ const {startRun} = await import('../../foundation/release/handoff.mjs');
32
+ const answered = await startRun({cliRoot, argv, declinedProjectCli});
33
+ if (answered) return answered;
34
+
35
+ const {
36
+ program,
37
+ loadProjectDebugHandler,
38
+ loadProjectForHelp,
39
+ handleVersionJsonPreflight,
40
+ } = await import('./index.mjs');
41
+ const {isJsonMode, toErrorEnvelope} =
42
+ await import('../../foundation/response/json.mjs');
43
+ const {handleCommanderError} = await import('./lib/json-shim.mjs');
44
+ const {setOutcome} = await import('../../foundation/debug/index.mjs');
45
+
46
+ /**
47
+ * Top-level error boundary (contract guarantee #4): an uncaught throw must
48
+ * never reach a --json consumer as a raw stack trace. If we're in JSON mode,
49
+ * convert it to the contract's error envelope on stdout and exit 1;
50
+ * otherwise preserve the normal human-facing behavior.
51
+ *
52
+ * We detect JSON mode two ways: the global flag (set once root options are
53
+ * parsed) and a raw argv check, since a throw can occur during
54
+ * parsing/command load — before the preAction hook engages the flag.
55
+ */
56
+ function inJsonMode() {
57
+ return isJsonMode() || process.argv.slice(2).includes('--json');
58
+ }
59
+
60
+ /** @param {unknown} err */
61
+ function handleFatal(err) {
62
+ // CommanderError (parse errors, --help, unknown command) routes
63
+ // through the JSON shim so that a --json consumer always gets a
64
+ // valid envelope and the right exit code. handleCommanderError
65
+ // calls process.exit when it owns the error.
66
+ if (handleCommanderError(err)) return;
67
+
68
+ // Anything still here is an uncaught throw or an unhandled rejection —
69
+ // a bug rather than a user error, and the class of failure most worth
70
+ // having a record of.
71
+ setOutcome('fatal', {exitCode: 1, error: err});
72
+
73
+ if (inJsonMode()) {
74
+ // Only emit if a command didn't already produce an envelope.
75
+ if (!process.__xdsJsonHandled) {
76
+ process.__xdsJsonHandled = true;
77
+ console.log(JSON.stringify(toErrorEnvelope(err), null, 2));
78
+ }
79
+ process.exit(1);
80
+ }
81
+ // Human mode: preserve Commander/Node default-ish behavior.
82
+ console.error(
83
+ err instanceof Error ? err.stack || err.message : String(err),
84
+ );
85
+ process.exit(1);
86
+ }
87
+
88
+ process.on('unhandledRejection', handleFatal);
89
+ process.on('uncaughtException', handleFatal);
90
+
91
+ // Pick up the project's `debug` function before parsing, so parse errors
92
+ // and `--help` — which short-circuit before any hook runs — are reported
93
+ // too.
94
+ await loadProjectDebugHandler();
95
+ // Help prints its commands while Commander parses, so a help run reads the
96
+ // project's declared `cliCommand` here.
97
+ await loadProjectForHelp();
98
+
99
+ // `--version --json` answers and exits without Commander, so it runs here —
100
+ // after the handler exists, or the run would go unrecorded.
101
+ handleVersionJsonPreflight();
102
+
103
+ try {
104
+ await program.parseAsync(process.argv);
105
+ } catch (err) {
106
+ handleFatal(err);
107
+ }
108
+ }
@@ -4,15 +4,25 @@
4
4
  /**
5
5
  * Find all existing agent doc files in a directory, across EVERY location any
6
6
  * preset can write (see {@link AGENT_DOC_PATHS}: AGENTS.md, CLAUDE.md,
7
- * .claude/CLAUDE.md, .cursorrules, .hermes.md, HERMES.md).
7
+ * .claude/CLAUDE.md, GEMINI.md, .cursorrules, .hermes.md, HERMES.md).
8
8
  * @param {string} targetDir
9
9
  * @returns {string[]} Relative paths of existing agent doc files
10
10
  */
11
11
  export function discoverAgentDocs(targetDir: string): string[];
12
+ /**
13
+ * Whether `content` holds an Astryx block. A managed block that only imports
14
+ * another file (the one-line `@./AGENTS.md` that init writes to GEMINI.md) does
15
+ * not count: it holds nothing once that file has lost its block. A malformed
16
+ * block still counts, so a half-written file reads as set up.
17
+ *
18
+ * @param {string} content
19
+ * @returns {boolean}
20
+ */
21
+ export function holdsAstryxBlock(content: string): boolean;
12
22
  /**
13
23
  * Single source of truth for "is Astryx set up in this project?" — true when any
14
- * agent-doc file already carries the Astryx marker, i.e. `init` / `agent-docs`
15
- * has run. Reused by the init & upgrade commands, the per-command setup nudge
24
+ * agent-doc file already holds an Astryx block, i.e. `init` / `agent-docs` has
25
+ * run. Reused by the init & upgrade commands, the per-command setup nudge
16
26
  * (enforcement layer 3), and the cli postinstall nudge (layer 2). Core's
17
27
  * postinstall (separate package, layer 1) mirrors the same marker contract.
18
28
  *
@@ -68,6 +78,7 @@ export function shouldNudge({ scriptPath, npmCommand, isSetUp }?: {
68
78
  export const AGENTS_MD: "AGENTS.md";
69
79
  export const CLAUDE_MD: "CLAUDE.md";
70
80
  export const CLAUDE_DIR_MD: ".claude/CLAUDE.md";
81
+ export const GEMINI_MD: "GEMINI.md";
71
82
  export const CURSOR_RULES: ".cursorrules";
72
83
  export const HERMES_DOT_MD: ".hermes.md";
73
84
  export const HERMES_MD: "HERMES.md";
@@ -77,10 +88,12 @@ export const LEGACY_MARKER_START: "<!-- XDS:START -->";
77
88
  export const LEGACY_MARKER_END: "<!-- XDS:END -->";
78
89
  /**
79
90
  * The canonical set of EVERY location an --agent preset (or the default) can
80
- * write the Astryx block. SINGLE SOURCE OF TRUTH: discovery, removal, and the
81
- * `isAstryxInitialized` predicate all derive from this list, so "where init
82
- * writes" and "where we look" can never drift. (Explicit --agent-docs-path
83
- * targets are user-chosen and not enumerable here.)
91
+ * write a managed Astryx block — the full block, or the one-line import of
92
+ * AGENTS.md that the default writes to GEMINI.md. SINGLE
93
+ * SOURCE OF TRUTH: discovery, removal, and the `isAstryxInitialized` predicate
94
+ * all derive from this list, so "where init writes" and "where we look" can
95
+ * never drift. (Explicit --agent-docs-path targets are user-chosen and not
96
+ * enumerable here.)
84
97
  */
85
98
  export const AGENT_DOC_PATHS: string[];
86
99
  /**
@@ -29,6 +29,7 @@ import * as path from 'node:path';
29
29
  export const AGENTS_MD = 'AGENTS.md';
30
30
  export const CLAUDE_MD = 'CLAUDE.md';
31
31
  export const CLAUDE_DIR_MD = '.claude/CLAUDE.md'; // cross-platform literal
32
+ export const GEMINI_MD = 'GEMINI.md';
32
33
  export const CURSOR_RULES = '.cursorrules';
33
34
  export const HERMES_DOT_MD = '.hermes.md';
34
35
  export const HERMES_MD = 'HERMES.md';
@@ -41,15 +42,18 @@ export const LEGACY_MARKER_END = '<!-- XDS:END -->';
41
42
 
42
43
  /**
43
44
  * The canonical set of EVERY location an --agent preset (or the default) can
44
- * write the Astryx block. SINGLE SOURCE OF TRUTH: discovery, removal, and the
45
- * `isAstryxInitialized` predicate all derive from this list, so "where init
46
- * writes" and "where we look" can never drift. (Explicit --agent-docs-path
47
- * targets are user-chosen and not enumerable here.)
45
+ * write a managed Astryx block — the full block, or the one-line import of
46
+ * AGENTS.md that the default writes to GEMINI.md. SINGLE
47
+ * SOURCE OF TRUTH: discovery, removal, and the `isAstryxInitialized` predicate
48
+ * all derive from this list, so "where init writes" and "where we look" can
49
+ * never drift. (Explicit --agent-docs-path targets are user-chosen and not
50
+ * enumerable here.)
48
51
  */
49
52
  export const AGENT_DOC_PATHS = [
50
53
  AGENTS_MD, // Codex / ChatGPT / Muse / generic
51
54
  CLAUDE_MD, // Claude Code (root)
52
55
  CLAUDE_DIR_MD, // Claude Code (.claude/CLAUDE.md)
56
+ GEMINI_MD, // Gemini CLI
53
57
  CURSOR_RULES, // Cursor
54
58
  HERMES_DOT_MD, // Hermes
55
59
  HERMES_MD, // Hermes
@@ -64,7 +68,7 @@ export const INIT_MARKERS = [MARKER_START, LEGACY_MARKER_START];
64
68
  /**
65
69
  * Find all existing agent doc files in a directory, across EVERY location any
66
70
  * preset can write (see {@link AGENT_DOC_PATHS}: AGENTS.md, CLAUDE.md,
67
- * .claude/CLAUDE.md, .cursorrules, .hermes.md, HERMES.md).
71
+ * .claude/CLAUDE.md, GEMINI.md, .cursorrules, .hermes.md, HERMES.md).
68
72
  * @param {string} targetDir
69
73
  * @returns {string[]} Relative paths of existing agent doc files
70
74
  */
@@ -72,10 +76,33 @@ export function discoverAgentDocs(targetDir) {
72
76
  return AGENT_DOC_PATHS.filter(p => fs.existsSync(path.join(targetDir, p)));
73
77
  }
74
78
 
79
+ /**
80
+ * Whether `content` holds an Astryx block. A managed block that only imports
81
+ * another file (the one-line `@./AGENTS.md` that init writes to GEMINI.md) does
82
+ * not count: it holds nothing once that file has lost its block. A malformed
83
+ * block still counts, so a half-written file reads as set up.
84
+ *
85
+ * @param {string} content
86
+ * @returns {boolean}
87
+ */
88
+ export function holdsAstryxBlock(content) {
89
+ for (const marker of INIT_MARKERS) {
90
+ let at = content.indexOf(marker);
91
+ while (at !== -1) {
92
+ const from = at + marker.length;
93
+ const end = content.indexOf('<!--', from);
94
+ const inner = (end === -1 ? content.slice(from) : content.slice(from, end)).trim();
95
+ if (!/^@\S+$/.test(inner)) return true;
96
+ at = content.indexOf(marker, from);
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+
75
102
  /**
76
103
  * Single source of truth for "is Astryx set up in this project?" — true when any
77
- * agent-doc file already carries the Astryx marker, i.e. `init` / `agent-docs`
78
- * has run. Reused by the init & upgrade commands, the per-command setup nudge
104
+ * agent-doc file already holds an Astryx block, i.e. `init` / `agent-docs` has
105
+ * run. Reused by the init & upgrade commands, the per-command setup nudge
79
106
  * (enforcement layer 3), and the cli postinstall nudge (layer 2). Core's
80
107
  * postinstall (separate package, layer 1) mirrors the same marker contract.
81
108
  *
@@ -86,7 +113,7 @@ export function isAstryxInitialized(targetDir = process.cwd()) {
86
113
  for (const rel of discoverAgentDocs(targetDir)) {
87
114
  try {
88
115
  const content = fs.readFileSync(path.join(targetDir, rel), 'utf-8');
89
- if (INIT_MARKERS.some(m => content.includes(m))) {
116
+ if (holdsAstryxBlock(content)) {
90
117
  return true;
91
118
  }
92
119
  } catch {
@@ -1,6 +1,31 @@
1
1
  // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
2
  // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
3
 
4
+ /**
5
+ * Throw {@link AgentDocsHomeError} when `targetDir` is the home folder, or a
6
+ * settings folder in it outside a project.
7
+ * @param {string} targetDir
8
+ */
9
+ export function assertAgentDocsTarget(targetDir: string): void;
10
+ /**
11
+ * The folder a run started in `cwd` writes agent docs to: the nearest folder at
12
+ * or above it whose package.json declares an `@astryxdesign` package, else the
13
+ * nearest folder with any package.json. The search stops at a repository root
14
+ * and never reaches the home folder, so a stray `~/package.json` cannot pull
15
+ * the docs there.
16
+ * @param {string} cwd
17
+ * @returns {string | null} null when no folder it searches has a package.json;
18
+ * init then writes where it runs, as before.
19
+ */
20
+ export function resolveAgentDocsRoot(cwd: string): string | null;
21
+ /**
22
+ * The folder `init` run in `cwd` writes the agent docs to: the project root
23
+ * {@link resolveAgentDocsRoot} finds, else `cwd` itself. The one definition
24
+ * that init, its removal, and the per-command setup nudge all use.
25
+ * @param {string} cwd
26
+ * @returns {string}
27
+ */
28
+ export function agentDocsTargetDir(cwd: string): string;
4
29
  /**
5
30
  * Parse the Astryx version a managed block was generated for, from its header
6
31
  * line ("Astryx v1.2.3 · N components"). Returns null when the block predates
@@ -23,6 +48,8 @@ export function parseBlockVersion(content: string): string | null;
23
48
  * - `current` — every managed block already matches the installed version.
24
49
  * And the project is `missing` when no managed block exists anywhere (the repo
25
50
  * has agent-doc files without our markers, or none at all — i.e. never `init`ed).
51
+ * A managed import of AGENTS.md is not a block here: it names no version and
52
+ * stays current while AGENTS.md holds the block.
26
53
  *
27
54
  * @param {string} targetDir
28
55
  * @param {string} [installedVersion] Defaults to the installed core version.
@@ -170,34 +197,70 @@ export function injectAgentsMd(targetDir: string, version: string): void;
170
197
  */
171
198
  export function injectClaudeMd(targetDir: string, version: string): boolean;
172
199
  /**
173
- * Remove Astryx section from a file.
174
- * If the file becomes empty (only boilerplate header remains), deletes it.
200
+ * Remove the Astryx-managed block, between its markers, from a file, leaving
201
+ * every other line as it is. With `deleteIfEmpty`, the file is deleted when
202
+ * nothing is left but whitespace or the exact heading init writes into a file
203
+ * it creates; with `deleteIfBlank`, only when nothing but whitespace is left.
175
204
  *
176
205
  * @param {string} filePath
177
- * @param {{deleteIfEmpty?: boolean}} [options]
206
+ * @param {{deleteIfEmpty?: boolean, deleteIfBlank?: boolean}} [options]
178
207
  * @returns {boolean} Whether the Astryx section was found and removed
179
208
  */
180
- export function removeXdsBlock(filePath: string, { deleteIfEmpty }?: {
209
+ export function removeXdsBlock(filePath: string, { deleteIfEmpty, deleteIfBlank }?: {
181
210
  deleteIfEmpty?: boolean;
211
+ deleteIfBlank?: boolean;
182
212
  }): boolean;
183
213
  /**
184
214
  * Remove Astryx section from all known agent doc files.
185
215
  * @param {string} targetDir
216
+ * @param {object} [options]
217
+ * @param {string} [options.shownFrom] - Folder the logged paths are shown
218
+ * relative to; defaults to `targetDir`.
219
+ * @param {Set<string>} [options.skip] - Files left as they are.
186
220
  * @returns {string[]} the project-relative files a block was removed from.
187
221
  * @throws {PathSafetyError} `ERR_PATH_TRAVERSAL` when a file it would change
188
222
  * resolves outside `targetDir`; nothing is changed.
189
223
  */
190
- export function removeAgentDocs(targetDir: string): string[];
224
+ export function removeAgentDocs(targetDir: string, { shownFrom, skip }?: {
225
+ shownFrom?: string | undefined;
226
+ skip?: Set<string> | undefined;
227
+ }): string[];
228
+ /**
229
+ * Remove the managed block where init writes it, the project root (see
230
+ * {@link resolveAgentDocsRoot}), and in `cwd`, so a block an earlier version
231
+ * wrote in that folder goes too. Each folder is checked before anything is
232
+ * changed. Run below the project root, a project-root agent file holding a
233
+ * block that resolves outside the project is left as it is and reported to
234
+ * `onSkipped`, as install does there; 0.7.x touched only `cwd`. A file in
235
+ * `cwd`, or at the root when run there, that resolves outside refuses the run.
236
+ *
237
+ * @param {string} cwd
238
+ * @param {{onSkipped?: (rel: string, reason: string) => void}} [options]
239
+ * @returns {string[]} the files a block was removed from, relative to `cwd`
240
+ * @throws {PathSafetyError} `ERR_PATH_TRAVERSAL`; nothing is changed
241
+ */
242
+ export function removeProjectAgentDocs(cwd: string, { onSkipped }?: {
243
+ onSkipped?: (rel: string, reason: string) => void;
244
+ }): string[];
191
245
  /**
192
246
  * Programmatic entry point for installing agent docs.
193
247
  * Used by the init command, upgrade command, and agent-docs command.
194
248
  *
195
- * Strategy (when no agent/paths specified):
196
- * - Discover all existing agent doc files.
197
- * - Leave `@path` import wrappers untouched; if an older run expanded a block
198
- * into one, remove that duplicate block.
199
- * - Initialize or refresh every standalone file.
200
- * - If nothing exists, create AGENTS.md as the tool-agnostic default.
249
+ * Default strategy (no agent/paths specified), so every agent reads the block:
250
+ * - Write the block to AGENTS.md (created when missing).
251
+ * - Write the block to the project's Claude Code file: root CLAUDE.md when it
252
+ * exists, else an existing .claude/CLAUDE.md, even when it imports AGENTS.md.
253
+ * Remove a block from the other one. Create no Claude file.
254
+ * - Write a managed import of AGENTS.md to GEMINI.md (created when missing),
255
+ * unless GEMINI.md already imports AGENTS.md.
256
+ * - Keep a full block in every other existing standalone file.
257
+ * - Leave every other `@path` import wrapper untouched; if an older run
258
+ * expanded a block into one, remove that duplicate block. When a wrapper
259
+ * imports a file other than AGENTS.md, the project keeps its own layout:
260
+ * every standalone file gets the block.
261
+ * A refresh (`onlyReplace`) rewrites existing blocks in place, the Claude
262
+ * file's copy included, leaves imports of AGENTS.md as they are, and creates
263
+ * nothing.
201
264
  *
202
265
  * @param {string} targetDir
203
266
  * @param {object} [options]
@@ -211,11 +274,21 @@ export function removeAgentDocs(targetDir: string): string[];
211
274
  * so an integration's topics reach the agent.
212
275
  * @param {string} [options.renderedBlock] - Fully rendered expected block. Init
213
276
  * and upgrade pass one shared block to every target.
277
+ * @param {(rel: string, reason: string) => void} [options.onSkipped] - Called
278
+ * for each file skipped because it resolves outside `targetDir` and 0.7.1
279
+ * never wrote it: GEMINI.md, or any file when `refuseEscapes` is false.
280
+ * @param {boolean} [options.refuseEscapes] - Whether a file 0.7.1 wrote that
281
+ * resolves outside `targetDir` refuses the run (default). Init passes false
282
+ * when it runs below the project root, where 0.7.1 wrote in the folder it
283
+ * ran in instead.
214
284
  * @returns {string[]} List of files written
215
285
  * @throws {import('../fs/path-safety.mjs').PathSafetyError} when a file it would
216
286
  * write resolves outside `targetDir`; nothing is written.
287
+ * @throws {AgentDocsHomeError} when `targetDir` is the home folder, or a settings
288
+ * folder in it outside a project, or, with `paths`, when a file's own folder
289
+ * is; nothing is written.
217
290
  */
218
- export function installAgentDocs(targetDir: string, { zh, lang, agent, paths, onlyReplace, topics, renderedBlock, }?: {
291
+ export function installAgentDocs(targetDir: string, { zh, lang, agent, paths, onlyReplace, topics, renderedBlock, onSkipped, refuseEscapes, }?: {
219
292
  zh?: boolean | undefined;
220
293
  lang?: string | undefined;
221
294
  agent?: string | undefined;
@@ -223,7 +296,22 @@ export function installAgentDocs(targetDir: string, { zh, lang, agent, paths, on
223
296
  onlyReplace?: boolean | undefined;
224
297
  topics?: string[] | undefined;
225
298
  renderedBlock?: string | undefined;
299
+ onSkipped?: ((rel: string, reason: string) => void) | undefined;
300
+ refuseEscapes?: boolean | undefined;
226
301
  }): string[];
302
+ /**
303
+ * Thrown when agent docs would be written into the home folder, or into a
304
+ * settings folder in it such as `~/.gemini` outside a project. Agents read the
305
+ * files there (for example `~/.claude/CLAUDE.md` or `~/.gemini/GEMINI.md`) in
306
+ * every project, so a project's version-pinned block never goes there. Removal
307
+ * stays allowed.
308
+ */
309
+ export class AgentDocsHomeError extends Error {
310
+ /** @param {string} dir */
311
+ constructor(dir: string);
312
+ /** The refused folder. */
313
+ dir: string;
314
+ }
227
315
  import { discoverAgentDocs } from './agent-doc-state.mjs';
228
316
  import { isAstryxInitialized } from './agent-doc-state.mjs';
229
317
  import { isAstryxPromptInReach } from './agent-doc-state.mjs';