@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cli/artifacts/classification.d.mts.map +1 -1
  3. package/dist/cli/artifacts/classification.mjs +10 -0
  4. package/dist/cli/artifacts/classification.mjs.map +1 -1
  5. package/dist/cli/commands/init.d.mts.map +1 -1
  6. package/dist/cli/commands/init.mjs +73 -5
  7. package/dist/cli/commands/init.mjs.map +1 -1
  8. package/dist/cli/commands/pr-review-audit-fixer.d.mts +41 -2
  9. package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
  10. package/dist/cli/commands/pr-review-audit-fixer.mjs +91 -14
  11. package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
  12. package/dist/cli/commands/route.d.mts.map +1 -1
  13. package/dist/cli/commands/route.mjs +10 -1
  14. package/dist/cli/commands/route.mjs.map +1 -1
  15. package/dist/cli/commands/task.d.mts.map +1 -1
  16. package/dist/cli/commands/task.mjs +28 -0
  17. package/dist/cli/commands/task.mjs.map +1 -1
  18. package/dist/cli/design/frontmatter-schema.d.mts +3 -3
  19. package/dist/cli/design/frontmatter-schema.d.mts.map +1 -1
  20. package/dist/cli/design/frontmatter-schema.mjs +3 -1
  21. package/dist/cli/design/frontmatter-schema.mjs.map +1 -1
  22. package/dist/cli/route/skill-discover.d.mts +2 -0
  23. package/dist/cli/route/skill-discover.d.mts.map +1 -1
  24. package/dist/cli/route/skill-discover.mjs +35 -1
  25. package/dist/cli/route/skill-discover.mjs.map +1 -1
  26. package/dist/cli/skill-author/contract.d.mts +19 -0
  27. package/dist/cli/skill-author/contract.d.mts.map +1 -1
  28. package/dist/cli/skill-author/contract.mjs +20 -0
  29. package/dist/cli/skill-author/contract.mjs.map +1 -1
  30. package/dist/cli/skill-author/skill-template.d.mts.map +1 -1
  31. package/dist/cli/skill-author/skill-template.mjs +4 -3
  32. package/dist/cli/skill-author/skill-template.mjs.map +1 -1
  33. package/package.json +5 -2
  34. package/skills/apt/SKILL.md +111 -5
  35. package/skills/apt-author-skill/SKILL.md +11 -0
  36. package/skills/apt-bootstrap/SKILL.md +1 -0
  37. package/skills/apt-classify/SKILL.md +1 -0
  38. package/skills/apt-close-task/SKILL.md +1 -0
  39. package/skills/apt-create-docs/SKILL.md +1 -0
  40. package/skills/apt-debug/SKILL.md +2 -0
  41. package/skills/apt-design/SKILL.md +2 -0
  42. package/skills/apt-discuss/SKILL.md +2 -0
  43. package/skills/apt-docs/SKILL.md +2 -0
  44. package/skills/apt-execute/SKILL.md +1 -0
  45. package/skills/apt-mockup/SKILL.md +2 -0
  46. package/skills/apt-pause/SKILL.md +1 -0
  47. package/skills/apt-personas/SKILL.md +1 -0
  48. package/skills/apt-plan/SKILL.md +2 -0
  49. package/skills/apt-pr-review/SKILL.md +1 -0
  50. package/skills/apt-quick/SKILL.md +2 -0
  51. package/skills/apt-resume/SKILL.md +1 -0
  52. package/skills/apt-review/SKILL.md +1 -0
  53. package/skills/apt-roadmap/SKILL.md +1 -0
  54. package/skills/apt-roundtable/SKILL.md +2 -0
  55. package/skills/apt-run/SKILL.md +1 -0
  56. package/skills/apt-scan/SKILL.md +1 -0
  57. package/skills/apt-setup/SKILL.md +1 -0
  58. package/skills/apt-ship/SKILL.md +6 -5
  59. package/skills/apt-stress-test/SKILL.md +1 -0
  60. package/skills/apt-terminal/SKILL.md +1 -0
  61. package/skills/apt-update/SKILL.md +3 -0
  62. package/skills/apt-verify/SKILL.md +1 -0
  63. package/skills/apt-verify-proof/SKILL.md +1 -0
  64. package/src/cli/artifacts/classification.mjs +10 -0
  65. package/src/cli/commands/init.mjs +83 -5
  66. package/src/cli/commands/pr-review-audit-fixer.mjs +95 -16
  67. package/src/cli/commands/route.mjs +10 -1
  68. package/src/cli/commands/task.mjs +27 -0
  69. package/src/cli/design/frontmatter-schema.mjs +3 -1
  70. package/src/cli/route/skill-discover.mjs +34 -1
  71. package/src/cli/skill-author/contract.mjs +22 -0
  72. package/src/cli/skill-author/skill-template.mjs +4 -3
@@ -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`, and `agent`.
143
+ set and filled in `skill`, `skill_args`, `spawn_agent`, `agent`, AND
144
+ `task_context` + (optional) `default_track`.
143
145
 
144
- **If `spawn_agent` is true:**
145
- Spawn the appropriate agent from the routing result. Pass `skill_args` as context.
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
- **If `spawn_agent` is false:**
148
- Invoke the skill directly: call `apt:{skill}` with `skill_args`.
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
@@ -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
@@ -9,6 +9,7 @@ user_invocable: false
9
9
  internal: true
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
@@ -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
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: true
11
11
  agent_name: apt-docs-author
12
+ task_context: create-new
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: DEBUG
12
14
  default_execution_mode: auto
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: STANDARD
12
14
  default_execution_mode: step
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: STANDARD
12
14
  default_execution_mode: step
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: STANDARD
12
14
  default_execution_mode: auto
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: true
11
11
  agent_name: "apt-executor"
12
+ task_context: require-existing
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: STANDARD
12
14
  default_execution_mode: step
13
15
  execution_modes:
14
16
  - auto
@@ -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
@@ -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
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: true
11
11
  agent_name: "apt-planner"
12
+ task_context: create-new
13
+ default_track: STANDARD
12
14
  default_execution_mode: auto
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: self-managed
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: QUICK
12
14
  default_execution_mode: auto
13
15
  execution_modes:
14
16
  - auto
@@ -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
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: true
11
11
  agent_name: "apt-reviewer"
12
+ task_context: require-existing
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -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: step
13
14
  execution_modes:
14
15
  - auto
@@ -9,6 +9,8 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: create-new
13
+ default_track: DEEP
12
14
  default_execution_mode: auto
13
15
  execution_modes:
14
16
  - auto
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: false
11
11
  agent_name: null
12
+ task_context: self-managed
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -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
@@ -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: step
13
14
  execution_modes:
14
15
  - auto
@@ -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 after Section 8 flips the task to `shipped-pending-merge` do NOT block the lifecycle transition. On `N` or any other response, proceed silently to Section 8.
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. Instead of closing the task now, flip its lifecycle to `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.
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
- ```bash
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: require-existing
12
13
  default_execution_mode: step
13
14
  execution_modes:
14
15
  - auto
@@ -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: step
13
14
  execution_modes:
14
15
  - auto
@@ -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`
@@ -9,6 +9,7 @@ user_invocable: true
9
9
  internal: false
10
10
  spawns_agent: true
11
11
  agent_name: "apt-verifier"
12
+ task_context: require-existing
12
13
  default_execution_mode: auto
13
14
  execution_modes:
14
15
  - auto
@@ -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
@@ -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(migration, collapsedEntries, source, templateVersion) {
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
- setAt(migration.migrated, desc.key, desc.default)
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: desc.default,
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
- applyCollapsedClassAToMigration(migration, requires, '--yes', templateConfig.version)
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