@aperant/framework 0.6.3 → 0.6.4
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/CHANGELOG.md +23 -0
- package/dist/cli/artifacts/classification.d.mts.map +1 -1
- package/dist/cli/artifacts/classification.mjs +10 -0
- package/dist/cli/artifacts/classification.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +73 -5
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.d.mts +41 -2
- package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.mjs +91 -14
- package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +10 -1
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +28 -0
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/design/frontmatter-schema.d.mts +3 -3
- package/dist/cli/design/frontmatter-schema.d.mts.map +1 -1
- package/dist/cli/design/frontmatter-schema.mjs +3 -1
- package/dist/cli/design/frontmatter-schema.mjs.map +1 -1
- package/dist/cli/route/skill-discover.d.mts +2 -0
- package/dist/cli/route/skill-discover.d.mts.map +1 -1
- package/dist/cli/route/skill-discover.mjs +35 -1
- package/dist/cli/route/skill-discover.mjs.map +1 -1
- package/dist/cli/skill-author/contract.d.mts +19 -0
- package/dist/cli/skill-author/contract.d.mts.map +1 -1
- package/dist/cli/skill-author/contract.mjs +20 -0
- package/dist/cli/skill-author/contract.mjs.map +1 -1
- package/dist/cli/skill-author/skill-template.d.mts.map +1 -1
- package/dist/cli/skill-author/skill-template.mjs +4 -3
- package/dist/cli/skill-author/skill-template.mjs.map +1 -1
- package/package.json +5 -2
- package/skills/apt/SKILL.md +111 -5
- package/skills/apt-author-skill/SKILL.md +11 -0
- package/skills/apt-bootstrap/SKILL.md +1 -0
- package/skills/apt-classify/SKILL.md +1 -0
- package/skills/apt-close-task/SKILL.md +1 -0
- package/skills/apt-create-docs/SKILL.md +1 -0
- package/skills/apt-debug/SKILL.md +2 -0
- package/skills/apt-design/SKILL.md +2 -0
- package/skills/apt-discuss/SKILL.md +2 -0
- package/skills/apt-docs/SKILL.md +2 -0
- package/skills/apt-execute/SKILL.md +1 -0
- package/skills/apt-mockup/SKILL.md +2 -0
- package/skills/apt-pause/SKILL.md +1 -0
- package/skills/apt-personas/SKILL.md +1 -0
- package/skills/apt-plan/SKILL.md +2 -0
- package/skills/apt-pr-review/SKILL.md +1 -0
- package/skills/apt-quick/SKILL.md +2 -0
- package/skills/apt-resume/SKILL.md +1 -0
- package/skills/apt-review/SKILL.md +1 -0
- package/skills/apt-roadmap/SKILL.md +1 -0
- package/skills/apt-roundtable/SKILL.md +2 -0
- package/skills/apt-run/SKILL.md +1 -0
- package/skills/apt-scan/SKILL.md +1 -0
- package/skills/apt-setup/SKILL.md +1 -0
- package/skills/apt-ship/SKILL.md +6 -5
- package/skills/apt-stress-test/SKILL.md +1 -0
- package/skills/apt-terminal/SKILL.md +1 -0
- package/skills/apt-update/SKILL.md +3 -0
- package/skills/apt-verify/SKILL.md +1 -0
- package/skills/apt-verify-proof/SKILL.md +1 -0
- package/src/cli/artifacts/classification.mjs +10 -0
- package/src/cli/commands/init.mjs +83 -5
- package/src/cli/commands/pr-review-audit-fixer.mjs +95 -16
- package/src/cli/commands/route.mjs +10 -1
- package/src/cli/commands/task.mjs +27 -0
- package/src/cli/design/frontmatter-schema.mjs +3 -1
- package/src/cli/route/skill-discover.mjs +34 -1
- package/src/cli/skill-author/contract.mjs +22 -0
- package/src/cli/skill-author/skill-template.mjs +4 -3
package/skills/apt/SKILL.md
CHANGED
|
@@ -9,6 +9,7 @@ user_invocable: true
|
|
|
9
9
|
internal: false
|
|
10
10
|
spawns_agent: false
|
|
11
11
|
agent_name: null
|
|
12
|
+
task_context: none
|
|
12
13
|
default_execution_mode: auto
|
|
13
14
|
execution_modes:
|
|
14
15
|
- auto
|
|
@@ -139,13 +140,118 @@ Show current state summary and autonomy flags (`--supervised`, `--guided`, `--au
|
|
|
139
140
|
|
|
140
141
|
The user invoked a specific skill by name (e.g., `/apt plan`, `/apt resume`,
|
|
141
142
|
`/apt:ship`). The router matched the first word against the installed skill
|
|
142
|
-
set and filled in `skill`, `skill_args`, `spawn_agent`,
|
|
143
|
+
set and filled in `skill`, `skill_args`, `spawn_agent`, `agent`, AND
|
|
144
|
+
`task_context` + (optional) `default_track`.
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
Dispatch on `task_context` — never just on `spawn_agent`. The four-branch
|
|
147
|
+
policy below is hardcoded contract; do NOT improvise. See
|
|
148
|
+
`docs/frameworks/spec-gaps.md#g25` for the defect that motivated this.
|
|
146
149
|
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
### Missing `task_context` — fail-closed
|
|
151
|
+
|
|
152
|
+
When the envelope's `task_context` is null (the `?? null` fallback in
|
|
153
|
+
`route.mjs:skillIndex.taskContextOf`), print verbatim and STOP. Do NOT
|
|
154
|
+
invoke the skill:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
[APT] ERROR — skill "apt:{skill}" has no task_context declared in SKILL.md frontmatter.
|
|
158
|
+
Cannot dispatch safely until the policy is set. Run /apt:author-skill validate
|
|
159
|
+
packages/framework/skills/apt-{skill}/SKILL.md to see required keys.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Note:** In practice, a skill missing `task_context` is dropped by
|
|
163
|
+
`discoverSkills` during the Zod schema validation step and will never reach
|
|
164
|
+
the skill-passthrough branch — it will be absent from `knownSlugs`. The
|
|
165
|
+
user sees the skill disappear silently from routing; run
|
|
166
|
+
`apt-tools route .` to surface dropped skills logged to
|
|
167
|
+
`.aperant/logs/route-dropped.jsonl`. The `task_context: null` path is
|
|
168
|
+
reachable if the field is marked optional in a future schema version —
|
|
169
|
+
this error fires in that case.
|
|
170
|
+
|
|
171
|
+
### `task_context = none`
|
|
172
|
+
|
|
173
|
+
No task semantics. Invoke the skill directly with `skill_args` (or spawn
|
|
174
|
+
`agent` when `spawn_agent` is true). No `task create`, no worktree banner.
|
|
175
|
+
|
|
176
|
+
### `task_context = self-managed`
|
|
177
|
+
|
|
178
|
+
The skill manages its own task / worktree lifecycle (e.g. `apt:run` calls
|
|
179
|
+
`task create` in its own Step 2; `apt:pr-review` sets up a PR-scoped
|
|
180
|
+
worktree). Dispatch identically to `none` — the skill's own SKILL.md is
|
|
181
|
+
responsible for any task record + worktree banner.
|
|
182
|
+
|
|
183
|
+
### `task_context = create-new`
|
|
184
|
+
|
|
185
|
+
Resolve the track, then create a fresh task record before invoking the skill.
|
|
186
|
+
|
|
187
|
+
**Track resolution chain (first match wins):**
|
|
188
|
+
|
|
189
|
+
1. Explicit user flag in `skill_args` — `--quick` → `QUICK`, `--deep` → `DEEP`,
|
|
190
|
+
`--debug` → `DEBUG`.
|
|
191
|
+
2. `envelope.default_track` if present (the skill's declared default).
|
|
192
|
+
3. Per-slug hardcoded fallback:
|
|
193
|
+
- `quick` → `QUICK`
|
|
194
|
+
- `debug` → `DEBUG`
|
|
195
|
+
- `plan` → `STANDARD`
|
|
196
|
+
- everything else with `task_context: create-new` → `STANDARD`
|
|
197
|
+
|
|
198
|
+
Then run:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
node packages/framework/bin/apt-tools.mjs task create . --description "{slug}" --track {track} --autonomy {autonomy} [--scope {scope}]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Security note:** Do NOT interpolate `{skill_args}` directly into `--description "..."`. `skill_args` is verbatim user input and can contain double-quote characters that break out of the argument boundary and inject extra flags (e.g. `--autonomy`, `--scope`). The task slug is sufficient as the description; `skill_args` is passed to the skill separately after the task record is created.
|
|
205
|
+
|
|
206
|
+
(Use `--scope framework` only when the user is on a framework-scoped task —
|
|
207
|
+
otherwise omit and let the default `project` scope apply.)
|
|
208
|
+
|
|
209
|
+
Parse `task_id`, `task_dir`, and (if present) `worktree.worktree_path` /
|
|
210
|
+
`worktree.branch` / `worktree.base_branch` from the result. Print the
|
|
211
|
+
worktree banner when `worktree` is returned:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
[APT] Working in isolated worktree: {worktree_path}
|
|
215
|
+
[APT] Task branch: {branch} (from {base_branch})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Then invoke `apt:{skill}` with `skill_args` + the new task context
|
|
219
|
+
(`task_id`, `task_dir`, `worktree_path`). When `spawn_agent` is true, spawn
|
|
220
|
+
`agent` with the same context.
|
|
221
|
+
|
|
222
|
+
### `task_context = require-existing`
|
|
223
|
+
|
|
224
|
+
Resolve which active task this invocation refers to, then dispatch. Read
|
|
225
|
+
`state.active_tasks` from `.aperant/state.json`.
|
|
226
|
+
|
|
227
|
+
**Active-task resolution chain (first match wins):**
|
|
228
|
+
|
|
229
|
+
1. `--task-id <id>` flag present in `skill_args` → use it. If `<id>` is not
|
|
230
|
+
in `state.active_tasks`, error and stop.
|
|
231
|
+
2. `state.active_tasks` has exactly one entry AND that entry's
|
|
232
|
+
`lifecycle_phase` is non-terminal (`planning`, `executing`, `verifying`,
|
|
233
|
+
`reviewing`) → use it. If the single entry is in a terminal phase
|
|
234
|
+
(`shipped-pending-merge` or `rejected`), fall through to step 4.
|
|
235
|
+
3. `state.active_tasks` has more than one entry with exactly one in a
|
|
236
|
+
non-terminal lifecycle phase (`planning`, `executing`, `verifying`,
|
|
237
|
+
`reviewing`) → use it.
|
|
238
|
+
(Terminal phases in `active_tasks` are `shipped-pending-merge` and
|
|
239
|
+
`rejected`. Note: `completed` tasks are removed from `active_tasks`
|
|
240
|
+
entirely at close and can never appear here.)
|
|
241
|
+
4. Otherwise, print the numbered menu and STOP. Auto-mode (autonomy 2/3)
|
|
242
|
+
does NOT bypass this prompt — multi-task ambiguity is the user's call,
|
|
243
|
+
never the framework's guess:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
[APT] Multiple active tasks — pick one with `--task-id <id>`:
|
|
247
|
+
1. {task-id-1} {description} ({lifecycle_phase})
|
|
248
|
+
2. {task-id-2} {description} ({lifecycle_phase})
|
|
249
|
+
...
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Once resolved, print the worktree banner if the task has a `worktree_path`,
|
|
253
|
+
then invoke `apt:{skill}` with `skill_args` + the resolved `task_id`. When
|
|
254
|
+
`spawn_agent` is true, spawn `agent` with the same context.
|
|
149
255
|
|
|
150
256
|
---
|
|
151
257
|
|
|
@@ -9,6 +9,7 @@ user_invocable: true
|
|
|
9
9
|
internal: false
|
|
10
10
|
spawns_agent: false
|
|
11
11
|
agent_name: null
|
|
12
|
+
task_context: create-new
|
|
12
13
|
default_execution_mode: step
|
|
13
14
|
execution_modes:
|
|
14
15
|
- auto
|
|
@@ -129,6 +130,16 @@ the author sees the full checklist of gaps to fill. Group by section:
|
|
|
129
130
|
The generated structural test only checks tags and numbered steps — the
|
|
130
131
|
TODOs are the semantic gaps the author must close.
|
|
131
132
|
|
|
133
|
+
**Required frontmatter field — `task_context`.** The scaffold ships with
|
|
134
|
+
`task_context: create-new` as the safest default (the new skill will get a
|
|
135
|
+
fresh task record + worktree via `apt-tools task create` when invoked as
|
|
136
|
+
`/apt <slug>`). Verify this is correct for your skill before merging — the
|
|
137
|
+
allowed values are `create-new | require-existing | self-managed | none`.
|
|
138
|
+
See `packages/framework/docs/skill-passthrough.md` for the four-policy
|
|
139
|
+
contract and the orchestrator dispatch behavior. Missing or wrong values
|
|
140
|
+
are caught by the structural test in
|
|
141
|
+
`packages/framework/src/__tests__/skill-frontmatter-task-context.test.ts`.
|
|
142
|
+
|
|
132
143
|
## 4. Show the Structural Test
|
|
133
144
|
|
|
134
145
|
Read the generated structural test file and echo its contents so the
|
package/skills/apt-docs/SKILL.md
CHANGED
package/skills/apt-plan/SKILL.md
CHANGED
package/skills/apt-run/SKILL.md
CHANGED
package/skills/apt-scan/SKILL.md
CHANGED
package/skills/apt-ship/SKILL.md
CHANGED
|
@@ -9,6 +9,7 @@ user_invocable: true
|
|
|
9
9
|
internal: false
|
|
10
10
|
spawns_agent: false
|
|
11
11
|
agent_name: null
|
|
12
|
+
task_context: require-existing
|
|
12
13
|
default_execution_mode: auto
|
|
13
14
|
execution_modes:
|
|
14
15
|
- auto
|
|
@@ -288,6 +289,8 @@ Capture the PR URL from the output into `pr_url` (for example, the last line of
|
|
|
288
289
|
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --pr-url {PR_URL}
|
|
289
290
|
```
|
|
290
291
|
|
|
292
|
+
This same call **atomically flips `lifecycle_phase` from `reviewing` to `shipped-pending-merge`** when the task is in `reviewing` and no explicit `--lifecycle-phase` flag is passed. Section 6 is now the single load-bearing step that both records the PR and transitions the lifecycle; Section 8 below documents the semantics but no longer issues a separate command.
|
|
293
|
+
|
|
291
294
|
Display the resolution so users see which branch/base was used:
|
|
292
295
|
|
|
293
296
|
```
|
|
@@ -338,7 +341,7 @@ suggest_mode=$(jq -r '.pr_review.suggest_after_ship // "always"' .aperant/config
|
|
|
338
341
|
```
|
|
339
342
|
[APT:ship] Run /apt:pr-review --pr {pr_number} now for deep multi-agent review? [Y/n]
|
|
340
343
|
```
|
|
341
|
-
On `Y` (or empty enter), invoke `/apt:pr-review --pr {pr_number}` inline
|
|
344
|
+
On `Y` (or empty enter), invoke `/apt:pr-review --pr {pr_number}` inline — the task has already been flipped to `shipped-pending-merge` atomically by the `--pr-url` recording in Section 6, so the deep-review hint does NOT need to block on Section 8. On `N` or any other response, proceed silently to Section 8.
|
|
342
345
|
|
|
343
346
|
- **`never`** — silent. Proceed directly to Section 8.
|
|
344
347
|
|
|
@@ -346,11 +349,9 @@ suggest_mode=$(jq -r '.pr_review.suggest_after_ship // "always"' .aperant/config
|
|
|
346
349
|
|
|
347
350
|
## 8. Transition lifecycle to `shipped-pending-merge` (C56 B5)
|
|
348
351
|
|
|
349
|
-
Ship is NOT the final closer. The PR has just been opened — it won't merge for minutes, hours, or days, and no agent will be present on merge.
|
|
352
|
+
Ship is NOT the final closer. The PR has just been opened — it won't merge for minutes, hours, or days, and no agent will be present on merge. The task lifecycle is now `shipped-pending-merge` — the intermediate state between `reviewing` and `completed`. `/apt:close-task` runs post-merge (either manually via `/apt:close-task --all` or by the user when they return to the repo) and performs the actual closeout: phase flip, documentation narration, worktree GC, per-PR config strip, task archive.
|
|
350
353
|
|
|
351
|
-
|
|
352
|
-
node packages/framework/bin/apt-tools.mjs task update . --id {task-id} --lifecycle-phase shipped-pending-merge
|
|
353
|
-
```
|
|
354
|
+
Section 6 already performed this transition atomically as part of `task update --pr-url`. This section documents the semantics; no separate command is required.
|
|
354
355
|
|
|
355
356
|
The task stays in `state.active_tasks[*]` with `lifecycle_phase: "shipped-pending-merge"`. Do NOT call `task close` here — that would mark it `completed` before the PR is actually merged, producing the same artifact drift C56 was built to eliminate.
|
|
356
357
|
|
|
@@ -9,6 +9,7 @@ user_invocable: true
|
|
|
9
9
|
internal: false
|
|
10
10
|
spawns_agent: false
|
|
11
11
|
agent_name: null
|
|
12
|
+
task_context: none
|
|
12
13
|
default_execution_mode: auto
|
|
13
14
|
execution_modes:
|
|
14
15
|
- auto
|
|
@@ -69,6 +70,8 @@ Build the flags list from the runtime ids (e.g. `--claude --codex`) and run:
|
|
|
69
70
|
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs init . <flags>
|
|
70
71
|
```
|
|
71
72
|
|
|
73
|
+
**Do NOT add `--yes`, `--solo`, `--team`, or any other Class A flag.** This command is meant to refresh runtime files only — adding `--yes` triggers a config schema migration that can silently flip `share.visibility` to `solo` and re-write `.gitignore`, which is exactly the 0.6.0/0.6.1 dogfood bug. If `init` returns a `requires_input` envelope (schema drift), surface it to the user verbatim and stop; let them decide team vs solo with the explicit flag. From 0.6.4 onward `--yes` infers visibility from existing git tracking, but the skill must still not add it — keep this command scoped to runtime refresh.
|
|
74
|
+
|
|
72
75
|
Parse the init JSON. Capture for each runtime:
|
|
73
76
|
- `install_root` (shortened to the basename, e.g. `.claude`)
|
|
74
77
|
- `file_count`
|
|
@@ -174,6 +174,11 @@ const RULES = Object.freeze([
|
|
|
174
174
|
class: ARTIFACT_CLASS.EPHEMERAL,
|
|
175
175
|
why: 'Append-only daily log of lifecycle transitions.',
|
|
176
176
|
},
|
|
177
|
+
{
|
|
178
|
+
pattern: '.aperant/install/**',
|
|
179
|
+
class: ARTIFACT_CLASS.EPHEMERAL,
|
|
180
|
+
why: 'Install telemetry/summaries (last-init/last-install) — runtime artifacts.',
|
|
181
|
+
},
|
|
177
182
|
{
|
|
178
183
|
pattern: '.aperant/team-status/**',
|
|
179
184
|
class: ARTIFACT_CLASS.EPHEMERAL,
|
|
@@ -249,6 +254,11 @@ const RULES = Object.freeze([
|
|
|
249
254
|
class: ARTIFACT_CLASS.EPHEMERAL,
|
|
250
255
|
why: 'Design-scan outputs (tokens, extracted repo, synthesis) — rebuildable.',
|
|
251
256
|
},
|
|
257
|
+
{
|
|
258
|
+
pattern: '.aperant/product-context.json',
|
|
259
|
+
class: ARTIFACT_CLASS.EPHEMERAL,
|
|
260
|
+
why: 'Derived/read-through sidecar used by CLI flows; rebuilt from onboarding context.',
|
|
261
|
+
},
|
|
252
262
|
// ── Explicit ephemeral: secrets/tokens MUST never classify committable ─
|
|
253
263
|
{
|
|
254
264
|
pattern: '.aperant/secrets/**',
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* half is replaced.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { execSync } from 'node:child_process'
|
|
18
19
|
import {
|
|
19
20
|
copyFileSync,
|
|
20
21
|
existsSync,
|
|
@@ -222,6 +223,51 @@ function buildRerunWithHint(projectDir, parsedFlags, requiresExplicit) {
|
|
|
222
223
|
return `apt-tools init ${projectDir} ${flags.join(' ')}`.trim()
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Infer `share.visibility` from the target's git tracking state.
|
|
228
|
+
*
|
|
229
|
+
* Repos that already track files inside `.aperant/` or any runtime root
|
|
230
|
+
* (`.claude/`, `.codex/`, …) are *behaviourally* team-mode: contributors
|
|
231
|
+
* pull the framework's installed files via git, not via local
|
|
232
|
+
* `apt-tools init`. Treating them as solo on an upgrade (the old `--yes`
|
|
233
|
+
* default did exactly this — bumped the schema, silently wrote
|
|
234
|
+
* `visibility: 'solo'`, and the project-gitignore allow-list flipped)
|
|
235
|
+
* is a destructive, non-obvious surprise. The 0.6.0/0.6.1 dogfood
|
|
236
|
+
* incidents both hit this rake.
|
|
237
|
+
*
|
|
238
|
+
* Returns:
|
|
239
|
+
* - `'team'` when any tracked file lives under `.aperant/` or a runtime
|
|
240
|
+
* root.
|
|
241
|
+
* - `'solo'` when the dir is a git repo but no such tracked file
|
|
242
|
+
* exists.
|
|
243
|
+
* - `undefined` when the target isn't a git repo (caller falls back to
|
|
244
|
+
* the descriptor default, which preserves greenfield behaviour).
|
|
245
|
+
*
|
|
246
|
+
* @param {string} targetDir
|
|
247
|
+
* @returns {'team'|'solo'|undefined}
|
|
248
|
+
*/
|
|
249
|
+
function inferVisibilityFromGit(targetDir) {
|
|
250
|
+
try {
|
|
251
|
+
execSync('git rev-parse --is-inside-work-tree 2>/dev/null', {
|
|
252
|
+
cwd: targetDir,
|
|
253
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
254
|
+
})
|
|
255
|
+
} catch {
|
|
256
|
+
return undefined
|
|
257
|
+
}
|
|
258
|
+
const probePaths = ['.aperant', ...RUNTIMES.map((r) => r.installRoot)]
|
|
259
|
+
try {
|
|
260
|
+
const out = execSync(`git ls-files -- ${probePaths.map((p) => `'${p}'`).join(' ')}`, {
|
|
261
|
+
cwd: targetDir,
|
|
262
|
+
encoding: 'utf8',
|
|
263
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
264
|
+
}).trim()
|
|
265
|
+
return out.length > 0 ? 'team' : 'solo'
|
|
266
|
+
} catch {
|
|
267
|
+
return 'solo'
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
225
271
|
/**
|
|
226
272
|
* Apply Class A defaults that were collapsed under --yes /
|
|
227
273
|
* --accept-new-defaults back into the migrated config so finishInit
|
|
@@ -243,21 +289,36 @@ function buildRerunWithHint(projectDir, parsedFlags, requiresExplicit) {
|
|
|
243
289
|
* writes — keeps the prototype-pollution guard and dot-path semantics in
|
|
244
290
|
* one place.
|
|
245
291
|
*
|
|
292
|
+
* `overrides` lets callers substitute the descriptor default for a
|
|
293
|
+
* specific shortKey — used in the upgrade path to honour
|
|
294
|
+
* `inferVisibilityFromGit`, so a team repo that ran `--yes` doesn't get
|
|
295
|
+
* silently flipped to solo.
|
|
296
|
+
*
|
|
246
297
|
* @param {{ migrated: Record<string, unknown>, applied_defaults: Array<{ key: string, value: unknown, why: string, source?: string }> }} migration
|
|
247
298
|
* @param {Array<{ key: string, why: string, default?: unknown }>} collapsedEntries
|
|
248
299
|
* @param {string} source `--yes` or `--accept-new-defaults`.
|
|
249
300
|
* @param {unknown} templateVersion Template's version, applied to migrated.
|
|
301
|
+
* @param {Record<string, unknown>} [overrides] shortKey → inferred value.
|
|
250
302
|
*/
|
|
251
|
-
function applyCollapsedClassAToMigration(
|
|
303
|
+
function applyCollapsedClassAToMigration(
|
|
304
|
+
migration,
|
|
305
|
+
collapsedEntries,
|
|
306
|
+
source,
|
|
307
|
+
templateVersion,
|
|
308
|
+
overrides = {},
|
|
309
|
+
) {
|
|
252
310
|
for (const r of collapsedEntries) {
|
|
253
311
|
const desc = findClassDescriptor(r.key)
|
|
254
312
|
if (!desc) continue
|
|
255
|
-
|
|
313
|
+
const hasOverride = Object.hasOwn(overrides, desc.shortKey)
|
|
314
|
+
const value = hasOverride ? overrides[desc.shortKey] : desc.default
|
|
315
|
+
const recordedSource = hasOverride ? `${source} (inferred from git tracking)` : source
|
|
316
|
+
setAt(migration.migrated, desc.key, value)
|
|
256
317
|
migration.applied_defaults.push({
|
|
257
318
|
key: desc.shortKey,
|
|
258
|
-
value
|
|
319
|
+
value,
|
|
259
320
|
why: r.why,
|
|
260
|
-
source,
|
|
321
|
+
source: recordedSource,
|
|
261
322
|
})
|
|
262
323
|
}
|
|
263
324
|
if (templateVersion !== undefined) {
|
|
@@ -318,7 +379,24 @@ function nonInteractiveAnswers(targetDir, canonicalRoot, archetype, parsedFlags)
|
|
|
318
379
|
// solo-mode — re-introducing the 0.6.0 dogfood bug.
|
|
319
380
|
let requires = migration.requires_explicit
|
|
320
381
|
if (parsedFlags.yes && requires.length > 0) {
|
|
321
|
-
|
|
382
|
+
// Prefer evidence over descriptor defaults: a repo that already
|
|
383
|
+
// tracks `.aperant/` or any runtime root is team-mode by
|
|
384
|
+
// behaviour. Without this, `--yes` flips long-tracked repos to
|
|
385
|
+
// solo on the v3→v4 schema bump (0.6.0/0.6.1 dogfood incidents).
|
|
386
|
+
// Explicit --solo/--team in parsedFlags wins; only collapse-time
|
|
387
|
+
// inference runs here.
|
|
388
|
+
const overrides = {}
|
|
389
|
+
if (!parsedFlags.visibility && requires.some((r) => r.key === 'visibility')) {
|
|
390
|
+
const inferred = inferVisibilityFromGit(targetDir)
|
|
391
|
+
if (inferred !== undefined) overrides.visibility = inferred
|
|
392
|
+
}
|
|
393
|
+
applyCollapsedClassAToMigration(
|
|
394
|
+
migration,
|
|
395
|
+
requires,
|
|
396
|
+
'--yes',
|
|
397
|
+
templateConfig.version,
|
|
398
|
+
overrides,
|
|
399
|
+
)
|
|
322
400
|
requires = []
|
|
323
401
|
} else if (parsedFlags.accept_new_defaults && requires.length > 0) {
|
|
324
402
|
// --accept-new-defaults applies template defaults for new Class A
|