@aperant/framework 0.8.0 → 0.8.1

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 (39) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +12 -0
  3. package/dist/cli/commands/init.d.mts.map +1 -1
  4. package/dist/cli/commands/init.mjs +67 -27
  5. package/dist/cli/commands/init.mjs.map +1 -1
  6. package/dist/cli/design/frontmatter-schema.d.mts +4 -4
  7. package/dist/cli/install/cli-to-install-root.d.mts.map +1 -1
  8. package/dist/cli/install/cli-to-install-root.mjs +0 -2
  9. package/dist/cli/install/cli-to-install-root.mjs.map +1 -1
  10. package/dist/cli/install/runtime-detect.d.mts +2 -6
  11. package/dist/cli/install/runtime-detect.d.mts.map +1 -1
  12. package/dist/cli/install/runtime-detect.mjs +8 -3
  13. package/dist/cli/install/runtime-detect.mjs.map +1 -1
  14. package/dist/cli/install/transforms/pi.d.mts +6 -0
  15. package/dist/cli/install/transforms/pi.d.mts.map +1 -0
  16. package/dist/cli/install/transforms/pi.mjs +37 -0
  17. package/dist/cli/install/transforms/pi.mjs.map +1 -0
  18. package/dist/cli/personas/sidecar.d.mts +1 -1
  19. package/dist/cli/roadmap/lifecycle.d.mts +1 -1
  20. package/dist/cli/roadmap/rollup.d.mts +2 -2
  21. package/dist/plugin/.claude-plugin/plugin.json +2 -1
  22. package/dist/plugin/agents/apt-executor.md +12 -0
  23. package/dist/plugin/agents/apt-team-docs-narrator.md +11 -0
  24. package/dist/plugin/skills/apt/SKILL.md +3 -2
  25. package/dist/plugin/skills/apt-execute/SKILL.md +13 -0
  26. package/dist/plugin/skills/apt-fan-out/SKILL.md +124 -0
  27. package/dist/plugin/skills/apt-pr-review/SKILL.md +8 -1
  28. package/dist/plugin/skills/apt-review/SKILL.md +4 -0
  29. package/dist/plugin/skills/apt-run/SKILL.md +20 -5
  30. package/dist/plugin/skills/apt-spar/SKILL.md +12 -9
  31. package/dist/plugin/skills/apt-verify/SKILL.md +11 -0
  32. package/dist/plugin/skills/apt-watch-ci/SKILL.md +2 -2
  33. package/dist/schemas/quick-task.d.ts +17 -17
  34. package/package.json +1 -1
  35. package/skills/apt/SKILL.md +3 -2
  36. package/src/cli/commands/init.mjs +66 -27
  37. package/src/cli/install/cli-to-install-root.mjs +0 -2
  38. package/src/cli/install/runtime-detect.mjs +8 -3
  39. package/src/cli/install/transforms/pi.mjs +37 -0
@@ -3,8 +3,9 @@
3
3
  * rewrite, 2026-04).
4
4
  *
5
5
  * Replaces the previous Claude-only `cp -r` installer with the per-runtime
6
- * transform pipeline (src/cli/install/pipeline.mjs). All 14 runtime targets
7
- * supported via `--<runtime>` flags or `--all`. Default with no flags in
6
+ * transform pipeline (src/cli/install/pipeline.mjs). Registered runtime
7
+ * targets are supported via `--<runtime>` flags or `--all`. Default with no
8
+ * flags in
8
9
  * non-TTY mode → `--claude` (backward compat — existing skills invoke
9
10
  * `apt-tools init <dir>` with no flags). Default in TTY → interactive prompt
10
11
  * with auto-detection markers.
@@ -40,12 +41,13 @@ import { buildAperantGitignoreTemplate } from '../config/upgrade-gitignore.mjs'
40
41
  import { detectPublicRemote } from '../git/remote.mjs'
41
42
  import { detectArchetype } from '../install/archetype.mjs'
42
43
  import { installKernel } from '../install/install-kernel.mjs'
44
+ import { MANIFEST_FILENAME } from '../install/manifest.mjs'
43
45
  import {
44
46
  discoverCanonicalFiles,
45
47
  runInstall,
46
48
  writeLastInstallSummary,
47
49
  } from '../install/pipeline.mjs'
48
- import { RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs'
50
+ import { getRuntime, RUNTIMES, suggestRuntimes } from '../install/runtime-detect.mjs'
49
51
  import { runLegacyCleanup } from '../install/runtime-migrate.mjs'
50
52
  import {
51
53
  CLASS_A_SETTINGS,
@@ -392,6 +394,42 @@ function selectRuntimes(targetDir, parsedFlags) {
392
394
  return [...suggested]
393
395
  }
394
396
 
397
+ /**
398
+ * Return the runtime ids that should be installed during a no-schema-drift
399
+ * init. A current config is not enough to prove runtime files exist on this
400
+ * machine: team repos often carry `.aperant/config.json` but not local
401
+ * runtime roots/manifests.
402
+ *
403
+ * @param {string} targetDir
404
+ * @param {string[]} runtimeIds
405
+ * @returns {string[]}
406
+ */
407
+ function runtimesMissingManifest(targetDir, runtimeIds) {
408
+ const missing = []
409
+ for (const id of runtimeIds) {
410
+ const runtime = getRuntime(id)
411
+ if (!runtime) continue
412
+ const manifestPath = join(targetDir, runtime.installRoot, MANIFEST_FILENAME)
413
+ if (!existsSync(manifestPath)) missing.push(id)
414
+ }
415
+ return missing
416
+ }
417
+
418
+ /**
419
+ * Current-schema init should still run the runtime installer when the user
420
+ * explicitly requested runtimes, when --force-runtimes is present, or when
421
+ * local runtime roots are suggested but have no apt manifest.
422
+ *
423
+ * @param {string} targetDir
424
+ * @param {ReturnType<typeof parseInstallFlags>} parsedFlags
425
+ * @returns {string[]}
426
+ */
427
+ function selectNoDriftRuntimeInstallIds(targetDir, parsedFlags) {
428
+ if (parsedFlags.forceRuntimes) return selectRuntimes(targetDir, parsedFlags)
429
+ if (parsedFlags.runtimes_explicit) return parsedFlags.runtimes
430
+ return runtimesMissingManifest(targetDir, suggestRuntimes(targetDir))
431
+ }
432
+
395
433
  /**
396
434
  * Non-TTY / scripted path — derive answers from flags + sensible defaults,
397
435
  * gated by the Class A/B classifier (spec R2 + R3).
@@ -663,11 +701,16 @@ async function interactiveInterview(targetDir, version, canonicalRoot, archetype
663
701
  migration.applied_defaults.length === 0 &&
664
702
  migration.auto_added.length === 0
665
703
  if (noDrift) {
666
- // 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses the TTY
667
- // "Nothing to do." outro so the runtime walker re-stamps every
668
- // manifest; cmdInit's force-runtimes synthesis path takes over.
669
- if (parsedFlags.forceRuntimes) {
670
- return { upgrade_noop: true, skipBootstrap, migration }
704
+ const runtimeInstallIds = selectNoDriftRuntimeInstallIds(targetDir, parsedFlags)
705
+ // Runtime repair bypasses the TTY "Nothing to do." outro so the
706
+ // runtime walker can re-stamp missing manifests first.
707
+ if (runtimeInstallIds.length > 0) {
708
+ return {
709
+ upgrade_noop: true,
710
+ skipBootstrap,
711
+ migration,
712
+ runtime_reinstall_runtimes: runtimeInstallIds,
713
+ }
671
714
  }
672
715
  process.stdout.write(`\n${aperantBanner(version)}\n\n`)
673
716
  clack.intro('install')
@@ -1158,20 +1201,20 @@ export async function cmdInit(projectDir, extraArgs = []) {
1158
1201
  )
1159
1202
  }
1160
1203
 
1161
- // Upgrade no-drift — short-circuit before installRuntimes.
1162
- // /apt:update is the right surface for refreshing skills on a
1163
- // no-drift run; init's job here is to confirm the schema is current.
1204
+ const noDriftRuntimeInstallIds = answers?.upgrade_noop
1205
+ ? (answers.runtime_reinstall_runtimes ??
1206
+ selectNoDriftRuntimeInstallIds(installTarget, parsedFlags))
1207
+ : []
1208
+
1209
+ // Upgrade no-drift with no runtime work — short-circuit before
1210
+ // installRuntimes. /apt:update remains the right surface for refreshing
1211
+ // already-manifested skills; init only repairs selected or suggested
1212
+ // runtime roots that lack manifests.
1164
1213
  // The bootstrap auto-chain still runs (it's deterministic and
1165
1214
  // idempotent — preserves PROJECT.md, refreshes sidecar from it).
1166
1215
  // In TTY mode clack already rendered "Nothing to do." to stdout,
1167
1216
  // so suppress the JSON envelope (matches the cancelled path).
1168
- //
1169
- // 0.6.7 (Bug 2 fix) — `--force-runtimes` bypasses this short-circuit
1170
- // so `/apt:update` can re-stamp every runtime manifest even when
1171
- // `.aperant/config.json` is at the current schema. We synthesize a
1172
- // real InstallAnswers shape from the existing config so
1173
- // installRuntimes + finishInit run their normal upgrade path.
1174
- if (answers?.upgrade_noop && !parsedFlags.forceRuntimes) {
1217
+ if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length === 0) {
1175
1218
  if (interactiveTty) return rawText('', 0)
1176
1219
  const bootstrap = answers.skipBootstrap
1177
1220
  ? { status: 'skipped', reason: '--skip-bootstrap flag' }
@@ -1187,18 +1230,14 @@ export async function cmdInit(projectDir, extraArgs = []) {
1187
1230
  })
1188
1231
  }
1189
1232
 
1190
- // 0.6.7 (Bug 2 fix) when forceRuntimes is set on a no-drift run,
1191
- // synthesize a real InstallAnswers shape from the existing config so
1192
- // the runtime walker re-stamps every manifest. Visibility/autonomy/
1193
- // cross-verification are read from answers.migration.migrated (already
1194
- // in memory from nonInteractiveAnswers — avoids a second config read);
1195
- // runtimes resolved via selectRuntimes helper shared with the upgrade path.
1233
+ // Current-schema runtime installsynthesize a real InstallAnswers
1234
+ // shape from the existing config so installRuntimes + finishInit run
1235
+ // their normal upgrade path.
1196
1236
  let effectiveAnswers = answers
1197
- if (answers?.upgrade_noop && parsedFlags.forceRuntimes) {
1237
+ if (answers?.upgrade_noop && noDriftRuntimeInstallIds.length > 0) {
1198
1238
  const existing = answers.migration?.migrated || {}
1199
- const runtimes = selectRuntimes(installTarget, parsedFlags)
1200
1239
  effectiveAnswers = {
1201
- runtimes,
1240
+ runtimes: noDriftRuntimeInstallIds,
1202
1241
  autonomyDefault: existing?.autonomy?.default ?? 1,
1203
1242
  visibility: existing?.share?.visibility === 'team' ? 'team' : 'solo',
1204
1243
  crossVerificationTools: existing?.multi_model?.cross_verification?.tools ?? [],
@@ -11,8 +11,6 @@
11
11
  * the lookups below work automatically.
12
12
  *
13
13
  * `null` is returned for:
14
- * - CLIs with no install target (e.g. `pi` — host detected, but no apt
15
- * install root exists for the Pi runtime).
16
14
  * - `'unknown'` — deterministic branches (init/status/help) don't need a
17
15
  * mapping; the router falls back to the framework-scoped dev default
18
16
  * defined in getTrustedSkillSources().
@@ -30,9 +30,7 @@ import { MANIFEST_FILENAME } from './manifest.mjs'
30
30
  * @property {string|null} cliId The `cli` string `detectHost()` returns
31
31
  * when this runtime's CLI is at the prompt.
32
32
  * `null` when the runtime has no install
33
- * target mapped to a host CLI (e.g. `pi`,
34
- * which has a detectable host but no apt
35
- * install root of its own).
33
+ * target mapped to a host CLI.
36
34
  * @property {string} [displayLabel] Optional user-facing label (e.g. "Kilo Code").
37
35
  * When unset, callers fall back to the
38
36
  * capitalized form of `id`. Use for runtimes
@@ -77,6 +75,13 @@ export const RUNTIMES = Object.freeze([
77
75
  markers: ['.opencode/config.json'],
78
76
  cliId: 'opencode',
79
77
  },
78
+ {
79
+ id: 'pi',
80
+ flag: '--pi',
81
+ installRoot: '.pi',
82
+ markers: ['.pi/settings.json', '.pi/skills', '.pi/prompts'],
83
+ cliId: 'pi',
84
+ },
80
85
  {
81
86
  id: 'kilo',
82
87
  flag: '--kilo',
@@ -0,0 +1,37 @@
1
+ /**
2
+ * install/transforms/pi.mjs — Pi coding agent transform.
3
+ *
4
+ * Pi uses prompt templates under `.pi/prompts/` for slash commands and
5
+ * root-level skills under `.pi/skills/`. Unlike Codex, Pi supports
6
+ * colon-form prompt names, so `/apt:foo` references are preserved.
7
+ */
8
+
9
+ import { basename, dirname } from 'node:path'
10
+ import { substituteVersion } from './_shared.mjs'
11
+
12
+ function promptNameForSkill(folder) {
13
+ if (folder === 'apt') return 'apt'
14
+ if (folder.startsWith('apt-')) return `apt:${folder.slice('apt-'.length)}`
15
+ return folder
16
+ }
17
+
18
+ /**
19
+ * @param {import('../pipeline.mjs').TransformInput} input
20
+ * @returns {import('../pipeline.mjs').TransformOutput[]}
21
+ */
22
+ export function transform({ canonicalContent, canonicalRelPath, version, fileType }) {
23
+ const content = substituteVersion(canonicalContent, version)
24
+ if (fileType === 'skill') {
25
+ const folder = basename(dirname(canonicalRelPath))
26
+ const promptName = promptNameForSkill(folder)
27
+ return [
28
+ { outputPath: `skills/${folder}/SKILL.md`, content },
29
+ { outputPath: `prompts/${promptName}.md`, content },
30
+ ]
31
+ }
32
+ if (fileType === 'agent') {
33
+ const file = basename(canonicalRelPath)
34
+ return [{ outputPath: `skills/${file}`, content }]
35
+ }
36
+ return []
37
+ }