@event4u/agent-config 9.10.0 → 9.11.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 (121) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +59 -0
  3. package/README.md +12 -15
  4. package/agents/templates/.ai-council.yml.example +481 -0
  5. package/agents/templates/.ai-video.xml.example +247 -0
  6. package/dist/agent-src/rules/design-fidelity.md +1 -1
  7. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +15 -2
  8. package/dist/agent-src/skills/fe-design/SKILL.md +10 -0
  9. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  10. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +30 -30
  11. package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +88 -33
  12. package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +20 -0
  13. package/dist/agent-src/templates/scripts/work_engine/directives/ui/review.ts +20 -1
  14. package/dist/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +179 -0
  15. package/dist/agent-src/templates/scripts/work_engine/stack/detect.ts +155 -4
  16. package/dist/agent-src/templates/scripts/work_engine/state.ts +21 -4
  17. package/dist/cli/initRouting.js +74 -11
  18. package/dist/cli/initRouting.js.map +1 -1
  19. package/dist/cli/main.js +14 -3
  20. package/dist/cli/main.js.map +1 -1
  21. package/dist/cli-delegate/chunk-4SV3XGFA.js +6910 -0
  22. package/dist/cli-delegate/chunk-736JCWLM.js +1104 -0
  23. package/dist/cli-delegate/chunk-7DSAMZFU.js +356 -0
  24. package/dist/cli-delegate/chunk-7HQ52DHU.js +74 -0
  25. package/dist/cli-delegate/chunk-BJGXZNPM.js +266 -0
  26. package/dist/cli-delegate/chunk-FTAISGDQ.js +117 -0
  27. package/dist/cli-delegate/chunk-GME3Q44L.js +82 -0
  28. package/dist/cli-delegate/chunk-GRRPEPRN.js +336 -0
  29. package/dist/cli-delegate/chunk-HJ6DAJHR.js +502 -0
  30. package/dist/cli-delegate/chunk-IR7P7JWA.js +872 -0
  31. package/dist/cli-delegate/chunk-J55WI3RR.js +47 -0
  32. package/dist/cli-delegate/chunk-MCJYCQPF.js +35 -0
  33. package/dist/cli-delegate/chunk-NDJLNC6Q.js +169 -0
  34. package/dist/cli-delegate/chunk-OMIVVCG2.js +26 -0
  35. package/dist/cli-delegate/chunk-OY53J6II.js +7336 -0
  36. package/dist/cli-delegate/chunk-RDGRHAAB.js +259 -0
  37. package/dist/cli-delegate/chunk-TFG57JJM.js +1969 -0
  38. package/dist/cli-delegate/chunk-XX6TOPD7.js +715 -0
  39. package/dist/cli-delegate/chunk-YMFO6FDN.js +10603 -0
  40. package/dist/cli-delegate/cmd_analyze_session.js +294 -0
  41. package/dist/cli-delegate/cmd_conformance.js +646 -0
  42. package/dist/cli-delegate/cmd_converge.js +326 -0
  43. package/dist/cli-delegate/cmd_doctor.js +144 -0
  44. package/dist/cli-delegate/cmd_explain.js +2508 -0
  45. package/dist/cli-delegate/cmd_export.js +296 -0
  46. package/dist/cli-delegate/cmd_fleet.js +332 -0
  47. package/dist/cli-delegate/cmd_memory_get.js +3878 -0
  48. package/dist/cli-delegate/cmd_migrate.js +11 -0
  49. package/dist/cli-delegate/cmd_preflight.js +270 -0
  50. package/dist/cli-delegate/cmd_prune.js +444 -0
  51. package/dist/cli-delegate/cmd_refresh.js +28 -0
  52. package/dist/cli-delegate/cmd_settings_check.js +574 -0
  53. package/dist/cli-delegate/cmd_settings_migrate.js +223 -0
  54. package/dist/cli-delegate/cmd_sync.js +264 -0
  55. package/dist/cli-delegate/cmd_uninstall.js +656 -0
  56. package/dist/cli-delegate/cmd_update.js +425 -0
  57. package/dist/cli-delegate/cmd_upgrade.js +444 -0
  58. package/dist/cli-delegate/cmd_validate.js +305 -0
  59. package/dist/cli-delegate/cmd_versions.js +370 -0
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +14 -7
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +2 -2
  64. package/dist/discovery/orphan-report.md +1 -1
  65. package/dist/discovery/packs.json +12 -7
  66. package/dist/discovery/trust-report.md +1 -1
  67. package/dist/discovery/workspaces.json +4 -3
  68. package/dist/install/emit_host_rules_cli.js +2 -1
  69. package/dist/install/emit_host_rules_cli.js.map +1 -1
  70. package/dist/install/install.mjs +410 -378
  71. package/dist/install/rule_scope.js +5 -1
  72. package/dist/install/rule_scope.js.map +1 -1
  73. package/dist/install/toolDetection.js +40 -12
  74. package/dist/install/toolDetection.js.map +1 -1
  75. package/dist/install/wizard-plan.js +2 -2
  76. package/dist/install/wizard-plan.js.map +1 -1
  77. package/dist/mcp/registry-manifest.json +1 -1
  78. package/dist/mcp/server.json +2 -2
  79. package/dist/mcp/server.mjs +1614 -765
  80. package/dist/router.json +1 -1
  81. package/package.json +4 -2
  82. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +30 -30
  83. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +88 -33
  84. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +20 -0
  85. package/src/agent-src/templates/scripts/work_engine/directives/ui/review.ts +20 -1
  86. package/src/agent-src/templates/scripts/work_engine/directives/ui/stack_bundles.ts +179 -0
  87. package/src/agent-src/templates/scripts/work_engine/stack/detect.ts +155 -4
  88. package/src/agent-src/templates/scripts/work_engine/state.ts +21 -4
  89. package/src/config/discovery/packs.yml +7 -0
  90. package/src/config/evaluator-budgets.json +1 -1
  91. package/src/config/pack-size-budget.json +27 -0
  92. package/src/install/emit_host_rules_cli.ts +2 -1
  93. package/src/install/rule_scope.ts +5 -1
  94. package/src/install/toolDetection.ts +39 -8
  95. package/src/install/wizard-plan.ts +2 -2
  96. package/src/scripts/_cli/cmd_conformance.ts +2 -1
  97. package/src/scripts/_cli/cmd_converge.ts +2 -1
  98. package/src/scripts/_cli/cmd_doctor.ts +214 -13
  99. package/src/scripts/_cli/cmd_export.ts +2 -1
  100. package/src/scripts/_cli/cmd_fleet.ts +2 -1
  101. package/src/scripts/_cli/cmd_migrate.ts +2 -1
  102. package/src/scripts/_cli/cmd_preflight.ts +2 -1
  103. package/src/scripts/_cli/cmd_refresh.ts +2 -1
  104. package/src/scripts/_cli/cmd_update.ts +2 -1
  105. package/src/scripts/_cli/cmd_upgrade.ts +2 -1
  106. package/src/scripts/_cli/cmd_versions.ts +2 -1
  107. package/src/scripts/_cli/detection_report.ts +480 -0
  108. package/src/scripts/_dispatch.bash +52 -14
  109. package/src/scripts/_lib/changelog_eras.ts +103 -9
  110. package/src/scripts/_lib/environment_detector.ts +441 -0
  111. package/src/scripts/_lib/package_root.ts +72 -0
  112. package/src/scripts/ai_council/config.ts +15 -7
  113. package/src/scripts/ai_council/modes.ts +68 -2
  114. package/src/scripts/ai_council/transport_resolver.ts +331 -0
  115. package/src/scripts/check_bundle_path_leakage.ts +10 -4
  116. package/src/scripts/check_dependency_floors.ts +153 -0
  117. package/src/scripts/check_pack_size.ts +188 -0
  118. package/src/scripts/council_cli.ts +6 -2
  119. package/src/scripts/evaluator_umbrella.sh +34 -1
  120. package/src/scripts/lint_eval_fixture_citations.ts +113 -0
  121. package/src/scripts/lint_ui_stack_bundles.ts +170 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Verb → skill-bundle map for the stack-dispatched UI steps.
3
+ *
4
+ * The UI steps halt with `@agent-directive: ui-apply-<stack>` (and the
5
+ * `ui-design-review-` / `ui-polish-` equivalents). Those names are **directive
6
+ * verbs, not skill paths** — the same shape as `run-tests`, `create-plan`, and
7
+ * `apply-plan`, which likewise resolve to no skill file. Only 2 of the engine's
8
+ * 11 literal verbs name a real skill, so a verb is something the agent
9
+ * interprets, and the mapping from verb to the skills that implement it is a
10
+ * separate fact that has to be written down somewhere.
11
+ *
12
+ * Before this module that mapping lived only in a prose table in
13
+ * `docs/contracts/ui-track-flow.md`. Nothing verified it, and two of its four
14
+ * rows were wrong: `vue` pointed at the directive verb itself (naming no skill
15
+ * at all) and `plain` — which is also the fallback for every unrecognised
16
+ * project — pointed at `blade-ui`, a skill whose frontmatter is
17
+ * `packs: [laravel]` and which a non-Laravel consumer therefore does not have
18
+ * installed.
19
+ *
20
+ * Keeping the map here makes it checkable: `lint_ui_stack_bundles` asserts every
21
+ * member resolves to a real skill, and that no lane reachable without the
22
+ * `laravel` pack names a `laravel`-only skill. A CI check in this repo cannot
23
+ * see a consumer's installed pack combination, so the second assertion is the
24
+ * part that actually prevents the `plain → blade-ui` class of defect.
25
+ */
26
+
27
+ /** Skills that implement a lane, in the order the agent should load them. */
28
+ export interface StackBundle {
29
+ /** Skills that build / fix UI for this lane (`apply`, `polish`). */
30
+ readonly build: ReadonlyArray<string>;
31
+ /** Skills that review UI for this lane (`review`). */
32
+ readonly review: ReadonlyArray<string>;
33
+ /**
34
+ * True when every skill above ships outside the framework packs, i.e. the
35
+ * lane is safe as a fallback for a consumer whose packs we cannot predict.
36
+ * Only lanes marked `pack_agnostic` may be used as `DEFAULT_*`.
37
+ */
38
+ readonly pack_agnostic: boolean;
39
+ }
40
+
41
+ /**
42
+ * The pack-agnostic pair. Both live in `engineering-base`, so they are present
43
+ * in every install; both self-describe as stack-neutral
44
+ * (`ui-component-architect`: "the same lens applies to Blade, Livewire, React,
45
+ * or Vue trees"; `tailwind-engineer`: "the **how** for any Tailwind-stack
46
+ * screen"). This is the honest floor for a lane with no framework-specific
47
+ * executor — not a claim that the lane is fully served.
48
+ */
49
+ const GENERIC_BUILD: ReadonlyArray<string> = ['ui-component-architect', 'tailwind-engineer'];
50
+
51
+ /** `design-review` is stack-neutral and ships in `engineering-base`. */
52
+ const GENERIC_REVIEW: ReadonlyArray<string> = ['design-review'];
53
+
54
+ /**
55
+ * Lane → bundle. Keys mirror `KNOWN_STACKS`; the dispatch tables in
56
+ * `apply` / `review` / `polish` keep their own verb strings (a tested public
57
+ * contract) and consume this map only to name the skills in the halt body.
58
+ */
59
+ export const STACK_BUNDLES: Readonly<Record<string, StackBundle>> = {
60
+ 'blade-livewire-flux': {
61
+ build: ['flux', 'livewire', 'blade-ui'],
62
+ review: GENERIC_REVIEW,
63
+ pack_agnostic: false,
64
+ },
65
+ // Livewire without Flux — the common Laravel frontend. Previously fell
66
+ // into `plain` because detection required Livewire AND Flux.
67
+ 'blade-livewire': {
68
+ build: ['livewire', 'blade-ui'],
69
+ review: GENERIC_REVIEW,
70
+ pack_agnostic: false,
71
+ },
72
+ // Filament is Livewire + Blade + Tailwind under the hood; the same
73
+ // executors apply. A Filament-specific skill is gated on evidence that
74
+ // these produce unusable output for it, not assumed.
75
+ filament: {
76
+ build: ['livewire', 'blade-ui'],
77
+ review: GENERIC_REVIEW,
78
+ pack_agnostic: false,
79
+ },
80
+ 'react-shadcn': {
81
+ build: ['react-shadcn-ui'],
82
+ review: GENERIC_REVIEW,
83
+ pack_agnostic: false,
84
+ },
85
+ // React without Radix/shadcn. `react-shadcn-ui` still carries the React
86
+ // component idiom; the shadcn-specific parts simply do not apply.
87
+ react: {
88
+ build: ['react-shadcn-ui'],
89
+ review: GENERIC_REVIEW,
90
+ pack_agnostic: false,
91
+ },
92
+ // No Vue executor exists. The previous prose row pointed at the directive
93
+ // verb itself, which named nothing; the generic pair is a smaller lie and a
94
+ // stated one — the halt body says the lane has no Vue-specific skill.
95
+ vue: {
96
+ build: GENERIC_BUILD,
97
+ review: GENERIC_REVIEW,
98
+ pack_agnostic: true,
99
+ },
100
+ plain: {
101
+ build: GENERIC_BUILD,
102
+ review: GENERIC_REVIEW,
103
+ pack_agnostic: true,
104
+ },
105
+ };
106
+
107
+ /**
108
+ * Lanes served by the stack-neutral pair rather than a framework executor.
109
+ *
110
+ * `plain` and `vue` share the bundle but not the meaning, and the halt body has
111
+ * to say which it is. For `plain` the pair IS the right answer — there is no
112
+ * framework to be idiomatic about. For `vue` it is a gap: a Vue project has a
113
+ * definite idiom and the package has no executor for it, so the result must not
114
+ * read as Vue support.
115
+ */
116
+ export const GENERIC_LANES: ReadonlySet<string> = new Set(['vue', 'plain']);
117
+
118
+ /** Lanes where the stack-neutral pair is the correct answer, not a shortfall. */
119
+ const NATIVELY_GENERIC_LANES: ReadonlySet<string> = new Set(['plain']);
120
+
121
+ /**
122
+ * The label detection returns for a framework it recognises but does not
123
+ * model. Mirrors `stack/detect.ts::UNSUPPORTED_STACK`; duplicated as a literal
124
+ * because this module is import-free by design (the detector is a leaf module
125
+ * the directives must not depend on).
126
+ */
127
+ export const UNSUPPORTED_LANE = 'unknown';
128
+
129
+ /** Return the bundle for `stack`, or the `plain` bundle when unmapped. */
130
+ export function bundle_for(stack: string): StackBundle {
131
+ return STACK_BUNDLES[stack] ?? (STACK_BUNDLES['plain'] as StackBundle);
132
+ }
133
+
134
+ /**
135
+ * Questions for the refusal halt on an unmodelled framework.
136
+ *
137
+ * Not a directive — there is nothing honest to dispatch to. The agent names
138
+ * the actual markers from the manifest (it has filesystem access; the engine
139
+ * deliberately does not carry them on `StackResult`), and the user chooses
140
+ * between the stack-neutral floor and stopping.
141
+ */
142
+ export function unsupported_stack_questions(step: string): string[] {
143
+ return [
144
+ '> Detection found a frontend framework this package does not model, ' +
145
+ `so there is no \`${step}\` lane for it.`,
146
+ '> Name the markers you see in `composer.json` / `package.json` so the ' +
147
+ 'choice below is made on the real stack, not on a guess.',
148
+ '> 1. Continue on the stack-neutral floor — `ui-component-architect` + ' +
149
+ '`tailwind-engineer`. Output will not be idiomatic for this ' +
150
+ 'framework; say so in the result.',
151
+ '> 2. Abort — drop this UI request and add a lane first ' +
152
+ '(`docs/contracts/ui-stack-extension.md`).',
153
+ ];
154
+ }
155
+
156
+ /**
157
+ * Render the "load these skills" line for a dispatch halt.
158
+ *
159
+ * The engine cannot load a skill itself; naming the bundle is what stops the
160
+ * agent from resolving an unresolvable verb by guessing. A lane with no
161
+ * framework-specific executor says that outright rather than presenting the
162
+ * generic pair as if it were stack support.
163
+ */
164
+ export function bundle_line(stack: string, role: 'build' | 'review'): string {
165
+ const bundle = bundle_for(stack);
166
+ const skills = role === 'review' ? bundle.review : bundle.build;
167
+ const rendered = skills.map((name) => `\`${name}\``).join(' + ');
168
+ if (NATIVELY_GENERIC_LANES.has(stack)) {
169
+ return `> Skills: ${rendered} — stack-neutral, which is what \`${stack}\` means.`;
170
+ }
171
+ if (GENERIC_LANES.has(stack)) {
172
+ return (
173
+ `> Skills: ${rendered} — there is no \`${stack}\`-specific executor, ` +
174
+ 'so this is the stack-neutral floor, not idiomatic ' +
175
+ `\`${stack}\`. Say so in the result.`
176
+ );
177
+ }
178
+ return `> Skills: ${rendered}.`;
179
+ }
@@ -17,17 +17,35 @@
17
17
  *
18
18
  * 1. `composer.json` lists `livewire/livewire` AND `livewire/flux` →
19
19
  * `blade-livewire-flux`
20
- * 2. `package.json` lists `react` AND any of `@radix-ui/*`,
20
+ * 2. `composer.json` lists `filament/filament` → `filament` (before bare
21
+ * Livewire: Filament pulls Livewire in transitively)
22
+ * 3. `composer.json` lists `livewire/livewire` → `blade-livewire`
23
+ * 4. `package.json` lists `react` AND any of `@radix-ui/*`,
21
24
  * `shadcn-ui` or has a `components.json` (the shadcn marker file)
22
25
  * → `react-shadcn`
23
- * 3. `package.json` lists `vue` (any major) → `vue`
24
- * 4. Otherwise → `plain`
26
+ * 5. `package.json` lists `react` → `react`
27
+ * 6. `package.json` lists `vue` (any major) → `vue`
28
+ * 7. Either manifest names a recognised-but-unmodelled framework
29
+ * (Svelte, Angular, Nuxt, Astro, Solid, Qwik, Inertia) → `unknown`
30
+ * 8. Otherwise → `plain`
31
+ *
32
+ * Rules 3, 5 and 7 exist because the earlier table sent three common
33
+ * shapes into `plain`: Laravel+Livewire without Flux failed rule 1's `&&`,
34
+ * React without Radix failed rule 4, and every unmodelled framework was
35
+ * indistinguishable from a genuinely plain project. `plain` now means "no
36
+ * frontend markers"; `unknown` means "a framework we do not model", and
37
+ * dispatch refuses on it rather than handing over generic tooling silently.
25
38
  *
26
39
  * Detection is filesystem-cheap: at most three small JSON reads per
27
40
  * project root. Errors (missing file, malformed JSON, missing `require`
28
41
  * section) downgrade to `plain` rather than raising — a wrong stack
29
42
  * label is recoverable (audit catches it, user can override), but a
30
43
  * crash mid-dispatch is not.
44
+ *
45
+ * Root manifests only, by design: a monorepo caller passes the workspace
46
+ * root that carries the manifest it cares about. Note the consequence —
47
+ * a caller that always passes the repository root gets `plain` for a
48
+ * monorepo, so the caller owns scope selection, not the detector.
31
49
  */
32
50
  import * as fs from 'node:fs';
33
51
  import * as path from 'node:path';
@@ -43,18 +61,59 @@ import * as path from 'node:path';
43
61
  */
44
62
  export const KNOWN_STACKS: ReadonlySet<string> = new Set([
45
63
  'blade-livewire-flux',
64
+ 'blade-livewire',
65
+ 'filament',
46
66
  'react-shadcn',
67
+ 'react',
47
68
  'vue',
48
69
  'plain',
70
+ 'unknown',
49
71
  ]);
50
72
 
51
- /** Fallback when no manifest signal matches. */
73
+ /**
74
+ * Label for a project with no frontend markers at all.
75
+ *
76
+ * `plain` used to carry two meanings — "genuinely a plain HTML/Tailwind
77
+ * project" and "detection failed, here is generic tooling anyway" — which made
78
+ * a loud failure impossible without punishing real plain projects. The second
79
+ * meaning is now {@link UNSUPPORTED_STACK}.
80
+ */
52
81
  export const DEFAULT_STACK = 'plain';
53
82
 
83
+ /**
84
+ * Label for a project whose frontend IS recognisable but is not modelled.
85
+ *
86
+ * Dispatch refuses on this label instead of degrading to generic tooling: a
87
+ * Filament or Svelte project silently routed to a Tailwind-only bundle is
88
+ * user-hostile precisely because the output looks like it worked.
89
+ */
90
+ export const UNSUPPORTED_STACK = 'unknown';
91
+
54
92
  const _SHADCN_RADIX_PREFIX = '@radix-ui/';
55
93
  const _SHADCN_PACKAGE_NAMES: ReadonlySet<string> = new Set(['shadcn-ui', 'shadcn']);
56
94
  const _FLUX_PACKAGE = 'livewire/flux';
57
95
  const _LIVEWIRE_PACKAGE = 'livewire/livewire';
96
+ const _FILAMENT_PACKAGE = 'filament/filament';
97
+
98
+ /**
99
+ * Frontend markers this package recognises but does not model.
100
+ *
101
+ * One of these present means detection **succeeded** at recognising a
102
+ * framework and the package simply has no lane for it — the `unknown` case,
103
+ * not the `plain` case. Keep the list short and factual; a marker leaves it
104
+ * only when a real lane exists.
105
+ */
106
+ const _UNMODELLED_MARKERS: ReadonlyArray<string> = [
107
+ 'svelte',
108
+ '@angular/core',
109
+ 'nuxt',
110
+ 'astro',
111
+ 'solid-js',
112
+ '@builder.io/qwik',
113
+ '@inertiajs/vue3',
114
+ '@inertiajs/react',
115
+ 'inertiajs/inertia-laravel',
116
+ ];
58
117
 
59
118
  /**
60
119
  * A heterogeneous JSON object, mirroring a Python `dict[str, object]`.
@@ -109,14 +168,51 @@ export function detect_stack(project_root: string): StackResult {
109
168
  return new StackResult({ frontend: 'blade-livewire-flux', mtime });
110
169
  }
111
170
 
171
+ // Filament before bare Livewire: it pulls Livewire in transitively, so a
172
+ // Filament project would otherwise be labelled by its dependency rather
173
+ // than by the framework the developer actually works in.
174
+ if (_has_package(composer, ['require', 'require-dev'], _FILAMENT_PACKAGE)) {
175
+ return new StackResult({ frontend: 'filament', mtime });
176
+ }
177
+
178
+ // Livewire WITHOUT Flux is the common Laravel frontend, not an unknown
179
+ // one. It used to fail the `&&` above and land in `plain`.
180
+ if (_has_package(composer, ['require', 'require-dev'], _LIVEWIRE_PACKAGE)) {
181
+ return new StackResult({ frontend: 'blade-livewire', mtime });
182
+ }
183
+
112
184
  if (_is_react_shadcn(pkg, components_json)) {
113
185
  return new StackResult({ frontend: 'react-shadcn', mtime });
114
186
  }
115
187
 
188
+ // React without Radix/shadcn is a served stack, not an unknown one.
189
+ if (_has_package(pkg, _PKG_DEP_KEYS, 'react')) {
190
+ return new StackResult({ frontend: 'react', mtime });
191
+ }
192
+
116
193
  if (_has_vue(pkg)) {
117
194
  return new StackResult({ frontend: 'vue', mtime });
118
195
  }
119
196
 
197
+ // A recognised-but-unmodelled framework is NOT `plain`. Refusing here is
198
+ // the point: `plain` means "no frontend markers", and conflating the two
199
+ // is what let a Svelte or Inertia project receive Tailwind-only tooling
200
+ // and no warning.
201
+ if (_has_unmodelled_marker(composer, pkg)) {
202
+ return new StackResult({ frontend: UNSUPPORTED_STACK, mtime });
203
+ }
204
+
205
+ // Monorepo: no manifest at the root, but one sits in a workspace directory.
206
+ // That is a wrong-scope call, not a plain project — the caller owns scope
207
+ // selection, so say so instead of labelling the whole repo `plain` and
208
+ // dispatching generic tooling at it.
209
+ //
210
+ // A repo with no manifest ANYWHERE stays `plain`: that is greenfield, and
211
+ // the scaffold path depends on it.
212
+ if (mtime === 0.0 && _has_nested_manifest(project_root)) {
213
+ return new StackResult({ frontend: UNSUPPORTED_STACK, mtime });
214
+ }
215
+
120
216
  return new StackResult({ frontend: DEFAULT_STACK, mtime });
121
217
  }
122
218
 
@@ -185,6 +281,61 @@ function _all_dependencies(manifest: Manifest, ...keys: string[]): Manifest {
185
281
  return merged;
186
282
  }
187
283
 
284
+ /** Dependency sections a `package.json` can carry. */
285
+ const _PKG_DEP_KEYS: ReadonlyArray<string> = [
286
+ 'dependencies',
287
+ 'devDependencies',
288
+ 'peerDependencies',
289
+ 'optionalDependencies',
290
+ ];
291
+
292
+ /** True when `name` appears in any of `manifest`'s named dependency sections. */
293
+ function _has_package(
294
+ manifest: Manifest,
295
+ sections: ReadonlyArray<string>,
296
+ name: string,
297
+ ): boolean {
298
+ return name in _all_dependencies(manifest, ...sections);
299
+ }
300
+
301
+ /** Workspace directories a monorepo conventionally puts its packages in. */
302
+ const _WORKSPACE_DIRS: ReadonlyArray<string> = ['packages', 'apps', 'services', 'libs'];
303
+
304
+ /**
305
+ * True when no root manifest exists but a workspace directory holds one.
306
+ *
307
+ * Deliberately shallow — one `readdir` per conventional workspace directory,
308
+ * one `stat` per child. It answers "was this called at the wrong scope?", not
309
+ * "where is the real project", which stays the caller's job.
310
+ */
311
+ function _has_nested_manifest(project_root: string): boolean {
312
+ for (const dir of _WORKSPACE_DIRS) {
313
+ const base = path.join(project_root, dir);
314
+ let entries: string[];
315
+ try {
316
+ entries = fs.readdirSync(base);
317
+ } catch {
318
+ continue;
319
+ }
320
+ for (const entry of entries) {
321
+ if (
322
+ _is_file(path.join(base, entry, 'composer.json')) ||
323
+ _is_file(path.join(base, entry, 'package.json'))
324
+ ) {
325
+ return true;
326
+ }
327
+ }
328
+ }
329
+ return false;
330
+ }
331
+
332
+ /** True when either manifest names a framework we recognise but do not model. */
333
+ function _has_unmodelled_marker(composer: Manifest, pkg: Manifest): boolean {
334
+ const composer_deps = _all_dependencies(composer, 'require', 'require-dev');
335
+ const pkg_deps = _all_dependencies(pkg, ..._PKG_DEP_KEYS);
336
+ return _UNMODELLED_MARKERS.some((marker) => marker in composer_deps || marker in pkg_deps);
337
+ }
338
+
188
339
  function _is_blade_livewire_flux(composer: Manifest): boolean {
189
340
  const deps = _all_dependencies(composer, 'require', 'require-dev');
190
341
  return _LIVEWIRE_PACKAGE in deps && _FLUX_PACKAGE in deps;
@@ -629,10 +629,12 @@ function _validate_app_spec(app_spec: JsonValue): void {
629
629
  * agent-directive that populates it. An empty dict is the in-progress
630
630
  * shape after the skill returns but before the brief lands; the gate
631
631
  * treats it the same as `null`. Once populated, `design_confirmed`
632
- * (when present) must be a bool. Other keys (`layout`, `components`,
633
- * `states`, `microcopy`, `a11y`, `reused_from_audit`) are
634
- * validated by the design handler against the skill contract — the
635
- * schema only enforces shape, not content.
632
+ * (when present) must be a bool, and `states` (when present and not
633
+ * `null`) must be an object — the design gate's per-state loop is
634
+ * object-guarded, so a string or list would bypass all five checks.
635
+ * The remaining keys (`layout`, `components`, `microcopy`, `a11y`,
636
+ * `reused_from_audit`) are validated by the design handler against the
637
+ * skill contract — the schema only enforces shape, not content.
636
638
  */
637
639
  function _validate_ui_design(ui_design: JsonValue): void {
638
640
  if (ui_design === null) {
@@ -652,6 +654,21 @@ function _validate_ui_design(ui_design: JsonValue): void {
652
654
  'state.ui_design.design_confirmed must be a boolean when present',
653
655
  );
654
656
  }
657
+ // `states` is shape-checked here, not left to the handler. The design
658
+ // gate's per-state loop was guarded on "is this an object", so a string or
659
+ // list silently skipped all five required-state checks — the gate passed
660
+ // while covering no state at all. Rejecting the wrong shape at the schema
661
+ // boundary means the loop can no longer be bypassed by type.
662
+ if (
663
+ 'states' in ui_design &&
664
+ ui_design['states'] !== null &&
665
+ !_isDict(ui_design['states'])
666
+ ) {
667
+ throw new SchemaError(
668
+ 'state.ui_design.states must be a JSON object (one key per ' +
669
+ `required state) or null; got ${pyTypeName(ui_design['states'])}`,
670
+ );
671
+ }
655
672
  }
656
673
 
657
674
  /**
@@ -10,6 +10,61 @@
10
10
  * unit-testable, separate from the self-executing CLI entry point.
11
11
  */
12
12
  import { isHeadless } from './commands/uiServe.js';
13
+ /** Flags that mean "the caller already knows what to install" → no picker. */
14
+ const CLI_SIGNALS = new Set([
15
+ '--no-ui', '--tools', '--ai', '--yes', '-y', '--quiet', '-q',
16
+ '--dry-run', '--minimal', '--settings-only', '--list-tools',
17
+ '--validate-only', '--fleet', '--project',
18
+ ]);
19
+ /** The explicit GUI opt-in documented in the README. */
20
+ const GUI_FLAG = '--gui';
21
+ /** `--flag=value` and `--flag value` both normalize to `--flag`. */
22
+ function flagOf(arg) {
23
+ return arg.split('=', 1)[0];
24
+ }
25
+ /** Whether the explicit `--gui` opt-in is present in `rest`. */
26
+ export function hasGuiFlag(rest) {
27
+ return rest.some((arg) => flagOf(arg) === GUI_FLAG);
28
+ }
29
+ /**
30
+ * `rest` with every `--gui` token removed. The bash installer's argument loop
31
+ * ends in `*) err "Unknown argument: $1"; exit 1`, so `--gui` must never reach
32
+ * it — `--gui` is an `init` front-end flag with no installer counterpart.
33
+ */
34
+ export function withoutGuiFlag(args) {
35
+ return args.filter((arg) => flagOf(arg) !== GUI_FLAG);
36
+ }
37
+ /**
38
+ * The conflict, if any, between an explicit `--gui` and an opt-out that beats
39
+ * it — returns a one-line reason, or `null` when there is no conflict.
40
+ *
41
+ * `--gui` overrides the *capability* probes (TTY, headless) but yields to the
42
+ * *intent* guards (`CI`, `AGENT_CONFIG_NO_UI`, and the CLI-mode flags). When
43
+ * intent wins, the explicit request is never discarded silently: the caller
44
+ * asked for the GUI in so many words, so `init` fails loudly instead of
45
+ * quietly doing the opposite (AI council 2026-07-31, Q1 option B + Q2).
46
+ *
47
+ * `rest` is argv without the leading `init` token.
48
+ */
49
+ export function findInitGuiConflict(rest) {
50
+ if (!hasGuiFlag(rest))
51
+ return null;
52
+ for (const arg of rest) {
53
+ const flag = flagOf(arg);
54
+ if (flag !== undefined && CLI_SIGNALS.has(flag)) {
55
+ return `--gui conflicts with ${flag}; drop one`;
56
+ }
57
+ }
58
+ const ci = (process.env['CI'] ?? '').trim();
59
+ if (ci && ci !== '0') {
60
+ return '--gui conflicts with CI=' + ci + ' in the environment; unset CI or drop --gui';
61
+ }
62
+ const envNoUi = (process.env['AGENT_CONFIG_NO_UI'] ?? '').trim();
63
+ if (envNoUi && envNoUi !== '0') {
64
+ return '--gui conflicts with AGENT_CONFIG_NO_UI=' + envNoUi + ' in the environment; unset it or drop --gui';
65
+ }
66
+ return null;
67
+ }
13
68
  /**
14
69
  * Whether `init` should open the browser wizard instead of the non-interactive
15
70
  * CLI install. Returns false (→ delegate to the bash CLI install) when ANY of
@@ -22,6 +77,16 @@ import { isHeadless } from './commands/uiServe.js';
22
77
  * --validate-only) — the caller already knows what to install and
23
78
  * doesn't want the picker.
24
79
  *
80
+ * An explicit `--gui` suppresses the two *capability* probes (TTY, headless)
81
+ * only. It does not defeat `CI`, `AGENT_CONFIG_NO_UI`, or a CLI-mode flag —
82
+ * those combinations are rejected up-front by `findInitGuiConflict`, so a
83
+ * `--gui` that reaches here and still loses is impossible.
84
+ *
85
+ * `--gui` deliberately does NOT imply `--allow-headless`: on a headless host
86
+ * `runUiServe` refuses with an actionable error naming `--allow-headless`,
87
+ * which beats booting a server that then waits for a browser that cannot
88
+ * arrive (AI council 2026-07-31, Q3).
89
+ *
25
90
  * `rest` is argv without the leading `init` token.
26
91
  */
27
92
  export function shouldInitLaunchGui(rest) {
@@ -31,18 +96,16 @@ export function shouldInitLaunchGui(rest) {
31
96
  const envNoUi = (process.env['AGENT_CONFIG_NO_UI'] ?? '').trim();
32
97
  if (envNoUi && envNoUi !== '0')
33
98
  return false;
34
- if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
35
- return false;
36
- if (isHeadless())
37
- return false;
38
- const cliSignals = new Set([
39
- '--no-ui', '--tools', '--ai', '--yes', '-y', '--quiet', '-q',
40
- '--dry-run', '--minimal', '--settings-only', '--list-tools',
41
- '--validate-only', '--fleet', '--project',
42
- ]);
99
+ const forced = hasGuiFlag(rest);
100
+ if (!forced) {
101
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true)
102
+ return false;
103
+ if (isHeadless())
104
+ return false;
105
+ }
43
106
  for (const arg of rest) {
44
- const flag = arg.split('=', 1)[0];
45
- if (flag !== undefined && cliSignals.has(flag))
107
+ const flag = flagOf(arg);
108
+ if (flag !== undefined && CLI_SIGNALS.has(flag))
46
109
  return false;
47
110
  }
48
111
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"initRouting.js","sourceRoot":"","sources":["../../src/cli/initRouting.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"initRouting.js","sourceRoot":"","sources":["../../src/cli/initRouting.ts"],"names":[],"mappings":"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"}
package/dist/cli/main.js CHANGED
@@ -22,7 +22,7 @@ import { runRecordTriggerEval } from './commands/recordTriggerEval.js';
22
22
  import { runDoctorShell } from './commands/doctorShell.js';
23
23
  import { runRtkDetect } from './commands/rtkDetect.js';
24
24
  import { runUiServe } from './commands/uiServe.js';
25
- import { shouldInitLaunchGui, buildInitGuiOptions, buildProjectInitDelegation } from './initRouting.js';
25
+ import { shouldInitLaunchGui, buildInitGuiOptions, buildProjectInitDelegation, findInitGuiConflict, withoutGuiFlag } from './initRouting.js';
26
26
  import { maybePrintFirstRunNotice } from './firstRunNotice.js';
27
27
  import { runSettings } from './commands/settings.js';
28
28
  import { runConfig } from './commands/config.js';
@@ -354,6 +354,15 @@ async function main(rawArgv) {
354
354
  // --project` writer — never the GUI, never the global install
355
355
  // (road-to-setup-experience § Phase 1.3).
356
356
  if (head === 'init') {
357
+ // `--gui` is the explicit opt-in. It overrides the capability probes
358
+ // (TTY, headless) but never `CI` / `AGENT_CONFIG_NO_UI` / a CLI-mode
359
+ // flag — and an explicit request is never discarded silently, so a
360
+ // losing `--gui` is a hard error rather than a quiet CLI install.
361
+ const conflict = findInitGuiConflict(argv.slice(1));
362
+ if (conflict !== null) {
363
+ logger.error(`init: ${conflict}`);
364
+ return 2;
365
+ }
357
366
  const projectDelegation = buildProjectInitDelegation(argv.slice(1));
358
367
  if (projectDelegation !== null) {
359
368
  return delegateToBash({ args: projectDelegation });
@@ -365,8 +374,10 @@ async function main(rawArgv) {
365
374
  logger.info('Opening the browser install wizard (set AGENT_CONFIG_NO_UI=1 or pass --no-ui for the CLI install).');
366
375
  return runUiServe(buildInitGuiOptions(argv.slice(1)));
367
376
  }
368
- // Everything else forwards to the Bash dispatcher verbatim.
369
- return delegateToBash({ args: argv });
377
+ // Everything else forwards to the Bash dispatcher. `--gui` is stripped:
378
+ // it has no installer counterpart and the bash argument loop ends in
379
+ // `err "Unknown argument"`.
380
+ return delegateToBash({ args: withoutGuiFlag(argv) });
370
381
  }
371
382
  main(process.argv.slice(2))
372
383
  .then((code) => {