raptor-aios 0.6.1 → 0.6.3

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 CHANGED
@@ -3,11 +3,36 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
+ ## [0.6.3] - 2026-06-06
7
+
8
+ ### Fixed
9
+
10
+ - **`raptor new --jira=<KEY>` no longer creates an empty, mislabelled spec when the Jira card can't be read.** Previously, when a ticket was given but Jira was not connected (or the token had expired, or the issue was unreachable), `new` warned and then degraded to seeding a spec stamped with the Jira ID but **no card content** — and created the branch too. Now, when a fetch was expected (the default), an unreadable card is a **hard stop**: `new` aborts **before any side effect** (no spec, no directory, no branch) with an actionable message that states the reason and points to `raptor jira connect` (or configuring the Jira MCP server). The intentional offline path is unchanged — pass `--no-jira-fetch` to record the ticket ID only.
11
+
12
+ ### Internal
13
+
14
+ - `New.fetchJiraContext` now returns a discriminated result (`{ ok, context }` | `{ ok: false, reason }`) so the caller can abort with the specific failure reason instead of silently degrading. Removed the now-unreachable `"id only (fetch unavailable)"` status tag.
15
+
16
+ ## [0.6.2] - 2026-06-06
17
+
18
+ ### Changed
19
+
20
+ - **`raptor upgrade` is now a real, non-destructive project sync.** Previously it only bumped `raptor_version` as a side effect of a manifest schema-version change, so a same-schema release (e.g. 0.5.0 → 0.6.x) left the project stamped at the old version and brought none of the framework updates. Now `raptor upgrade`:
21
+ - **Re-stamps the version unconditionally** in `raptor.manifest.json` and, surgically (single `version:` line), in `.raptor/raptor.yml` — preserving the user's `mcp`/`git`/`jira` sections **and comments** byte-for-byte (no template re-render).
22
+ - **Refreshes every framework-owned file** to the installed version — shell scripts, command templates, the rich slash commands (`specify`/`plan`/`tasks`/… now carry the design-gate instructions), and bundled extensions — **backing each up to `.raptor/backup/<timestamp>/` before overwriting**, and skipping files that are already current (idempotent).
23
+ - **Upgrades the constitution CORE block in place** when outdated (reusing the `repair constitution` mechanism): preserves preset (M*) and project (P*) articles, logs `constitution.upgraded`, and keeps `gate.constitution.integrity` / `gate.amendment.core` green. Requires a human approver (C5, `git user.email`); skips with a warning when absent or when the CORE was hand-edited (run `raptor repair constitution` first).
24
+ - **Never touches user-owned content**: `init-options.json`, `.raptor/specs/`, `.raptor/memory/*`, `.raptor/templates/overrides/`, custom extensions, and an existing `agents.yml` are left as-is. `--dry-run` previews the full change list and writes nothing.
25
+
26
+ ### Internal
27
+
28
+ - Extracted the framework-install routines (`installShellScripts`, `installTemplates`, `installBundledExtensions`, `collectFrameworkFiles`) into `packages/cli/src/shared/scaffold.ts`, shared by `init` and `upgrade` so an upgraded project matches a freshly-init'd one. Split the slash-command materializer into a pure `planSlashCommands` (used by upgrade's diff) plus the writing `materializeSlashCommands`.
29
+
6
30
  ## [0.6.1] - 2026-06-05
7
31
 
8
32
  ### Changed
9
33
 
10
- - Package `author` set to **Lucas Pedro <lucaspedrolbg@gmail.com> (https://github.com/lucaspedronet)** on the publishable package and the workspace `package.json` files (previously the bare npm handle). Metadata-only; no code changes.
34
+ - Package `author` set to **Lucas Pedro <lucaspedrolbg@gmail.com> (https://github.com/lucaspedronet)** on the publishable package and the workspace `package.json` files (previously the bare npm handle).
35
+ - **Slimmer published package.** The tarball no longer ships `.d.ts` type declarations, `.d.ts.map`/`.js.map` source maps, or `sourceMappingURL` pragmas, and the emitted `.js` is built with `removeComments` — removing the most readable/structured slice of the code (the typed API surface and the JSDoc rationale) and roughly halving the tarball. Runtime behaviour is unchanged. (Note: a public npm package can never truly hide code; this only reduces casual exposure.)
11
36
 
12
37
  ## [0.6.0] - 2026-06-05
13
38
 
@@ -15,4 +15,4 @@ export { validateHandoff, nextPhase, isTerminalPhase } from "./handoff.js";
15
15
  export { waitForArtifact, snapshotMtime, snapshotDir, resolveArtifactPath, watchTargetOf, relPath, ArtifactWaitTimeoutError, ArtifactWaitAbortedError, } from "./artifact-watcher.js";
16
16
  export { backupIfExists, createBackupSession, backupTimestamp, DEFAULT_BACKUP_SUBDIR, } from "./backup.js";
17
17
  export { MARKERS, wrapSpec, wrapUntrusted, sanitizeUntrusted, assertNoMarkers, extractSpec, extractUntrusted, hasSpecMarkers, } from "./prompt-markers.js";
18
- export { materializeSlashCommands, renderRichCommand, locateRichCommandsDir, } from "./slash-materializer.js";
18
+ export { materializeSlashCommands, planSlashCommands, renderRichCommand, locateRichCommandsDir, } from "./slash-materializer.js";
@@ -37,10 +37,9 @@ export function renderRichCommand(raw, opts) {
37
37
  c = c.replace(/\brpt\.([a-z-]+)/g, "raptor-$1");
38
38
  return c;
39
39
  }
40
- export function materializeSlashCommands(opts) {
40
+ export function planSlashCommands(opts) {
41
41
  const { projectRoot, adapter, constitutionArticles } = opts;
42
42
  const commandDir = join(projectRoot, adapter.commandDir);
43
- mkdirSync(commandDir, { recursive: true });
44
43
  const richDir = locateRichCommandsDir(projectRoot);
45
44
  const commands = richDir
46
45
  ? readdirSync(richDir)
@@ -48,7 +47,7 @@ export function materializeSlashCommands(opts) {
48
47
  .map((f) => f.replace(/\.md$/, ""))
49
48
  .sort()
50
49
  : knownCommands();
51
- const materialized = [];
50
+ const planned = [];
52
51
  for (const command of commands) {
53
52
  let content;
54
53
  const richPath = richDir ? join(richDir, `${command}.md`) : null;
@@ -68,16 +67,23 @@ export function materializeSlashCommands(opts) {
68
67
  content = adapter.packPrompt(canonical);
69
68
  }
70
69
  const fileName = adapter.commandFileName(command);
71
- const filePath = join(commandDir, fileName);
72
- writeFileSync(filePath, content);
73
- materialized.push({
70
+ planned.push({
74
71
  command,
75
- path: filePath,
72
+ path: join(commandDir, fileName),
73
+ content,
76
74
  hash: hashString(content),
77
75
  });
78
76
  }
79
- return {
80
- agentId: adapter.id,
81
- commands: materialized,
82
- };
77
+ return { agentId: adapter.id, commands: planned };
78
+ }
79
+ export function materializeSlashCommands(opts) {
80
+ const plan = planSlashCommands(opts);
81
+ const commandDir = join(opts.projectRoot, opts.adapter.commandDir);
82
+ mkdirSync(commandDir, { recursive: true });
83
+ const materialized = [];
84
+ for (const c of plan.commands) {
85
+ writeFileSync(c.path, c.content);
86
+ materialized.push({ command: c.command, path: c.path, hash: c.hash });
87
+ }
88
+ return { agentId: plan.agentId, commands: materialized };
83
89
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raptor/core",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js"
6
6
  }
@@ -1,9 +1,10 @@
1
1
  import { Flags } from "@oclif/core";
2
2
  import { BaseCommand } from "../base-command.js";
3
- import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync, } from "node:fs";
4
- import { basename, dirname, join } from "node:path";
3
+ import { chmodSync, existsSync, mkdirSync, writeFileSync, } from "node:fs";
4
+ import { basename, join } from "node:path";
5
5
  import { appendAuditEvent, buildContextBlock, coreBlock, CORE_VERSION, createManifest, detectInstalledAgents, generateAgentsYaml, getAgentByKind, getPreset, hashString, knownPresetIds, materializeSlashCommands, renderBundled, renderPresetArticles, SELECTABLE_AGENT_KEYS, upsertContextFile, VERSION, } from "../_core/dist/index.js";
6
6
  import { currentActor } from "../shared/project.js";
7
+ import { installBundledExtensions, installShellScripts, installTemplates, } from "../shared/scaffold.js";
7
8
  export default class Init extends BaseCommand {
8
9
  static description = "Initialize a Raptor project in the current directory";
9
10
  static examples = [
@@ -126,9 +127,9 @@ export default class Init extends BaseCommand {
126
127
  writeFileSync(join(raptorDir, "memory", "decisions.md"), "# Architecture Decision Records\n\n");
127
128
  writeFileSync(join(raptorDir, "memory", "glossary.md"), "# Project Glossary\n\n");
128
129
  writeFileSync(join(raptorDir, ".gitignore"), "cache/\n*.log\n");
129
- this.copyShellScripts(cwd, raptorDir, manifest);
130
- this.copyTemplates(raptorDir);
131
- this.installBundledExtensions(raptorDir, manifest);
130
+ installShellScripts(this, raptorDir, manifest);
131
+ installTemplates(raptorDir);
132
+ installBundledExtensions(this, raptorDir, manifest);
132
133
  const installedAgents = detectInstalledAgents();
133
134
  const primaryAgent = getAgentByKind(flags.ai);
134
135
  if (primaryAgent && !installedAgents.some((a) => a.id === primaryAgent.id)) {
@@ -273,101 +274,6 @@ export default class Init extends BaseCommand {
273
274
  }
274
275
  return { status: "ok", installed, skipped };
275
276
  }
276
- copyShellScripts(_cwd, raptorDir, manifest) {
277
- const pkgRoot = this.findRaptorPackageRoot();
278
- if (!pkgRoot) {
279
- this.warn("Could not locate Raptor package root — shell scripts not copied.");
280
- return;
281
- }
282
- const copied = [];
283
- const summary = [];
284
- const shells = [
285
- { dir: "bash", ext: ".sh" },
286
- { dir: "powershell", ext: ".ps1" },
287
- ];
288
- for (const shell of shells) {
289
- const sourceDir = join(pkgRoot, "scripts", shell.dir);
290
- const targetDir = join(raptorDir, "scripts", shell.dir);
291
- if (!existsSync(sourceDir))
292
- continue;
293
- mkdirSync(targetDir, { recursive: true });
294
- const files = readdirSync(sourceDir).filter((f) => f.endsWith(shell.ext));
295
- for (const file of files) {
296
- const src = join(sourceDir, file);
297
- const dest = join(targetDir, file);
298
- copyFileSync(src, dest);
299
- if (shell.ext === ".sh")
300
- chmodSync(dest, 0o755);
301
- copied.push(`scripts/${shell.dir}/${file}`);
302
- }
303
- if (files.length > 0) {
304
- summary.push(`${files.length} ${shell.dir}`);
305
- }
306
- }
307
- manifest.scripts_installed = copied;
308
- writeFileSync(join(raptorDir, "raptor.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
309
- if (summary.length > 0) {
310
- this.log(` Scripts: ${summary.join(", ")}`);
311
- }
312
- }
313
- copyTemplates(raptorDir) {
314
- const pkgRoot = this.findRaptorPackageRoot();
315
- if (!pkgRoot)
316
- return;
317
- const srcTemplates = join(pkgRoot, "templates");
318
- if (!existsSync(srcTemplates))
319
- return;
320
- const srcCommands = join(srcTemplates, "commands");
321
- if (existsSync(srcCommands)) {
322
- const destCommands = join(raptorDir, "templates", "commands");
323
- mkdirSync(destCommands, { recursive: true });
324
- for (const f of readdirSync(srcCommands).filter((f) => f.endsWith(".md"))) {
325
- copyFileSync(join(srcCommands, f), join(destCommands, f));
326
- }
327
- }
328
- const destTemplates = join(raptorDir, "templates");
329
- mkdirSync(destTemplates, { recursive: true });
330
- for (const f of readdirSync(srcTemplates).filter((f) => f.endsWith("-template.md"))) {
331
- copyFileSync(join(srcTemplates, f), join(destTemplates, f));
332
- }
333
- }
334
- installBundledExtensions(raptorDir, manifest) {
335
- const pkgRoot = this.findRaptorPackageRoot();
336
- if (!pkgRoot)
337
- return;
338
- const sourceExtRoot = join(pkgRoot, "extensions");
339
- if (!existsSync(sourceExtRoot))
340
- return;
341
- const registered = [];
342
- for (const extId of readdirSync(sourceExtRoot)) {
343
- const sourceExt = join(sourceExtRoot, extId);
344
- const manifestFile = join(sourceExt, "extension.yml");
345
- if (!existsSync(manifestFile))
346
- continue;
347
- const targetExt = join(raptorDir, "extensions", extId);
348
- copyDirRecursive(sourceExt, targetExt);
349
- registered.push(extId);
350
- }
351
- if (registered.length === 0)
352
- return;
353
- manifest.extensions_registered = registered;
354
- writeFileSync(join(raptorDir, "raptor.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
355
- this.log(` Ext: ${registered.length} bundled (${registered.join(", ")})`);
356
- }
357
- findRaptorPackageRoot() {
358
- let dir = dirname(new URL(import.meta.url).pathname);
359
- for (let i = 0; i < 10; i++) {
360
- if (existsSync(join(dir, "pnpm-workspace.yaml")) ||
361
- existsSync(join(dir, "scripts", "bash"))) {
362
- return dir;
363
- }
364
- const parent = dirname(dir);
365
- if (parent === dir)
366
- break;
367
- dir = parent;
368
- }
369
- return null;
370
- }
371
277
  }
372
278
  function describeCi(r) {
373
279
  if (r.status === "disabled")
@@ -388,16 +294,3 @@ function describeHooks(r) {
388
294
  parts.push(`skipped (already exist): ${r.skipped.join(", ")}`);
389
295
  return parts.length > 0 ? parts.join("; ") : "nothing to do";
390
296
  }
391
- function copyDirRecursive(source, target) {
392
- mkdirSync(target, { recursive: true });
393
- for (const entry of readdirSync(source)) {
394
- const src = join(source, entry);
395
- const dest = join(target, entry);
396
- if (statSync(src).isDirectory()) {
397
- copyDirRecursive(src, dest);
398
- }
399
- else {
400
- copyFileSync(src, dest);
401
- }
402
- }
403
- }
@@ -113,7 +113,26 @@ export default class New extends BaseCommand {
113
113
  const specNeedsSeeding = !existsSync(specPath);
114
114
  let jiraContext = null;
115
115
  if (specNeedsSeeding && flags.jira && flags["jira-fetch"]) {
116
- jiraContext = await this.fetchJiraContext(root, flags.jira);
116
+ const result = await this.fetchJiraContext(root, flags.jira);
117
+ if (result.ok) {
118
+ jiraContext = result.context;
119
+ }
120
+ else {
121
+ this.error([
122
+ `${result.reason}`,
123
+ ``,
124
+ `Aborting: --jira=${flags.jira} was given but its Jira card could not be read,`,
125
+ `so there is no card content to seed the spec from. No spec or branch was`,
126
+ `created — a spec stamped with a Jira ID but no Jira content would be misleading.`,
127
+ ``,
128
+ `To fix, do one of:`,
129
+ ` • Connect Jira, then retry:`,
130
+ ` raptor jira connect`,
131
+ ` (or configure the Jira MCP server under 'jira:' in .raptor/raptor.yml)`,
132
+ ` • Record the ticket ID only, without fetching the card:`,
133
+ ` raptor new ${args.slug} --jira=${flags.jira} --no-jira-fetch`,
134
+ ].join("\n"), { exit: 1 });
135
+ }
117
136
  }
118
137
  const designText = specNeedsSeeding
119
138
  ? [
@@ -271,9 +290,7 @@ export default class New extends BaseCommand {
271
290
  ? "seeded spec"
272
291
  : !specNeedsSeeding
273
292
  ? "id recorded — spec already authored, not re-seeded"
274
- : flags["jira-fetch"]
275
- ? "id only (fetch unavailable)"
276
- : "id only";
293
+ : "id only";
277
294
  this.log(` Jira: ${flags.jira} (${tag})`);
278
295
  }
279
296
  if (designContext.hasDesign) {
@@ -416,28 +433,27 @@ export default class New extends BaseCommand {
416
433
  async fetchJiraContext(root, key) {
417
434
  const conn = jiraConn(readConfig(root));
418
435
  if (!conn) {
419
- this.warn(`--jira=${key} given but Jira is not connected — recording the ID only. Run 'raptor jira connect' to seed specs.`);
420
- return null;
436
+ return { ok: false, reason: `Jira is not connected.` };
421
437
  }
422
438
  if (!jiraReadyNonInteractive(conn)) {
423
- this.warn(`Jira tokens missing run 'raptor jira connect'. Recording ID only.`);
424
- return null;
439
+ return { ok: false, reason: `Jira credentials are missing.` };
425
440
  }
426
441
  let client;
427
442
  try {
428
443
  client = await openJiraClient(conn);
429
444
  }
430
445
  catch {
431
- this.warn(`Jira session expired run 'raptor jira connect'. Recording ID only.`);
432
- return null;
446
+ return { ok: false, reason: `Jira session expired.` };
433
447
  }
434
448
  try {
435
449
  const issue = await client.getJiraIssue(conn.cloudId, key);
436
- return mapIssueToSpecContext(issue);
450
+ return { ok: true, context: mapIssueToSpecContext(issue) };
437
451
  }
438
452
  catch (err) {
439
- this.warn(`Could not fetch ${key} (${err instanceof Error ? err.message : String(err)}) — recording ID only.`);
440
- return null;
453
+ return {
454
+ ok: false,
455
+ reason: `Could not fetch ${key} from Jira (${err instanceof Error ? err.message : String(err)}).`,
456
+ };
441
457
  }
442
458
  finally {
443
459
  await client.close();
@@ -1,143 +1,292 @@
1
1
  import { Flags } from "@oclif/core";
2
2
  import { BaseCommand } from "../base-command.js";
3
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { MANIFEST_SCHEMA_VERSION, validateManifest, VERSION, } from "../_core/dist/index.js";
3
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { appendAuditEvent, buildContextBlock, coreHash, CORE_VERSION, createBackupSession, createManifest, detectInstalledAgents, extractCoreBlock, generateAgentsYaml, getAgentByKind, getPreset, hashString, loadAgentsConfig, MANIFEST_SCHEMA_VERSION, planSlashCommands, rewriteCoreBlock, upsertContextFile, validateManifest, VERSION, } from "../_core/dist/index.js";
6
+ import { collectFrameworkFiles, installBundledExtensions, installShellScripts, installTemplates, } from "../shared/scaffold.js";
7
+ import { currentActor, readInitOptions, restampYmlVersion, } from "../shared/project.js";
6
8
  export default class Upgrade extends BaseCommand {
7
- static description = "Migrate Raptor configuration to the latest version";
9
+ static description = "Sync the project to the installed Raptor version (non-destructive)";
8
10
  static examples = [
9
11
  "<%= config.bin %> upgrade",
10
12
  "<%= config.bin %> upgrade --dry-run",
11
13
  ];
12
14
  static flags = {
13
15
  "dry-run": Flags.boolean({
14
- description: "Show what would be changed without applying",
16
+ description: "Show what would change without writing anything",
15
17
  default: false,
16
18
  }),
17
19
  };
18
20
  async run() {
19
21
  const { flags } = await this.parse(Upgrade);
20
- const cwd = process.cwd();
21
- const raptorDir = join(cwd, ".raptor");
22
+ const dry = flags["dry-run"];
23
+ const root = process.cwd();
24
+ const raptorDir = join(root, ".raptor");
22
25
  if (!existsSync(raptorDir)) {
23
26
  this.error("No .raptor/ directory found. Run 'raptor init' first.");
24
27
  }
25
- const manifestPath = join(raptorDir, "raptor.manifest.json");
26
- const raptorYmlPath = join(raptorDir, "raptor.yml");
27
28
  const changes = [];
29
+ const skipped = [];
30
+ const session = dry ? null : createBackupSession(root);
31
+ const backup = (abs) => {
32
+ session?.backup(abs);
33
+ };
34
+ const hashOf = (p) => existsSync(p) ? hashString(readFileSync(p, "utf8")) : "";
35
+ const manifestPath = join(raptorDir, "raptor.manifest.json");
36
+ const ymlPath = join(raptorDir, "raptor.yml");
37
+ const initOpts = readInitOptions(root);
38
+ let manifest;
28
39
  if (!existsSync(manifestPath)) {
29
- this.log("📦 raptor.manifest.json not found — creating from scratch.");
30
40
  let projectName = "unknown";
31
- let preset = "mobile-opinionated";
32
- if (existsSync(raptorYmlPath)) {
33
- try {
34
- const yml = readFileSync(raptorYmlPath, "utf-8");
35
- const nameMatch = yml.match(/project_name:\s*["']?([^"'\n]+)/);
36
- const presetMatch = yml.match(/preset:\s*["']?([^"'\n]+)/);
37
- if (nameMatch)
38
- projectName = nameMatch[1].trim();
39
- if (presetMatch)
40
- preset = presetMatch[1].trim();
41
- }
42
- catch {
43
- }
41
+ if (existsSync(ymlPath)) {
42
+ const m = readFileSync(ymlPath, "utf8").match(/name:\s*["']?([^"'\n]+)/);
43
+ if (m)
44
+ projectName = m[1].trim();
44
45
  }
45
- const manifest = {
46
- schema_version: MANIFEST_SCHEMA_VERSION,
46
+ manifest = createManifest({
47
47
  raptor_version: VERSION,
48
48
  project_name: projectName,
49
- integration: "claude-code",
49
+ integration: initOpts?.ai ?? "claude-code",
50
50
  script_type: "sh",
51
- branch_numbering: "sequential",
52
- preset,
51
+ branch_numbering: initOpts?.branch_numbering ?? "sequential",
52
+ preset: initOpts?.preset ?? "mobile-opinionated",
53
53
  scripts_installed: [],
54
54
  templates_installed: [],
55
55
  extensions_registered: [],
56
- created_at: new Date().toISOString(),
57
- };
56
+ });
58
57
  changes.push("CREATE raptor.manifest.json (migrated from raptor.yml)");
59
- if (!flags["dry-run"]) {
60
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
61
- }
62
58
  }
63
59
  else {
64
- try {
65
- const raw = JSON.parse(readFileSync(manifestPath, "utf-8"));
66
- const result = validateManifest(raw);
67
- if (!result.valid) {
68
- this.warn("Manifest validation errors:");
69
- for (const err of result.errors) {
70
- this.warn(` - ${err}`);
71
- }
72
- changes.push("FIX manifest validation errors");
60
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
61
+ const result = validateManifest(manifest);
62
+ if (!result.valid) {
63
+ for (const err of result.errors)
64
+ this.warn(`manifest: ${err}`);
65
+ }
66
+ }
67
+ if (manifest.raptor_version !== VERSION) {
68
+ changes.push(`STAMP raptor_version: ${manifest.raptor_version} ${VERSION}`);
69
+ }
70
+ if (manifest.schema_version !== MANIFEST_SCHEMA_VERSION) {
71
+ changes.push(`BUMP schema_version: ${manifest.schema_version} → ${MANIFEST_SCHEMA_VERSION}`);
72
+ }
73
+ manifest.raptor_version = VERSION;
74
+ manifest.schema_version = MANIFEST_SCHEMA_VERSION;
75
+ const projectName = typeof manifest.project_name === "string"
76
+ ? manifest.project_name
77
+ : "project";
78
+ const presetId = typeof manifest.preset === "string" ? manifest.preset : undefined;
79
+ if (existsSync(ymlPath)) {
80
+ const raw = readFileSync(ymlPath, "utf8");
81
+ let next = restampYmlVersion(raw, VERSION);
82
+ next = next.replace(/\nagents:\s*\[\]\n(?:#[^\n]*\n)*/, "\n# Agent configuration lives in agents.yml — run 'raptor add-agent <key>'.\n");
83
+ if (next !== raw) {
84
+ changes.push("UPDATE raptor.yml (version + legacy cleanup)");
85
+ if (!dry) {
86
+ backup(ymlPath);
87
+ writeFileSync(ymlPath, next);
73
88
  }
74
- if (raw.schema_version !== MANIFEST_SCHEMA_VERSION) {
75
- changes.push(`BUMP schema_version: ${raw.schema_version} → ${MANIFEST_SCHEMA_VERSION}`);
76
- if (!flags["dry-run"]) {
77
- raw.schema_version = MANIFEST_SCHEMA_VERSION;
78
- raw.raptor_version = VERSION;
79
- raw.updated_at = new Date().toISOString();
80
- writeFileSync(manifestPath, JSON.stringify(raw, null, 2) + "\n");
81
- }
89
+ }
90
+ }
91
+ const fw = collectFrameworkFiles(raptorDir);
92
+ const changedFw = fw.filter((f) => !existsSync(f.dest) || hashOf(f.src) !== hashOf(f.dest));
93
+ if (changedFw.length > 0) {
94
+ changes.push(`REFRESH ${changedFw.length} framework file(s)`);
95
+ if (!dry) {
96
+ for (const f of changedFw)
97
+ backup(f.dest);
98
+ installShellScripts(this, raptorDir, manifest);
99
+ installTemplates(raptorDir);
100
+ installBundledExtensions(this, raptorDir, manifest);
101
+ }
102
+ }
103
+ const agents = this.resolveAgents(root, initOpts?.ai);
104
+ const presetArticles = presetId
105
+ ? getPreset(presetId)?.articles.map((a) => a.id)
106
+ : undefined;
107
+ let slashChanged = 0;
108
+ let ctxChanged = 0;
109
+ for (const adapter of agents) {
110
+ if (adapter.id === "human")
111
+ continue;
112
+ const plan = planSlashCommands({
113
+ projectRoot: root,
114
+ projectName,
115
+ adapter,
116
+ ...(presetArticles ? { constitutionArticles: presetArticles } : {}),
117
+ ...(presetId ? { presetId } : {}),
118
+ });
119
+ for (const c of plan.commands) {
120
+ if (existsSync(c.path) && hashOf(c.path) === c.hash)
121
+ continue;
122
+ slashChanged++;
123
+ if (!dry) {
124
+ backup(c.path);
125
+ mkdirSync(dirname(c.path), { recursive: true });
126
+ writeFileSync(c.path, c.content);
82
127
  }
83
128
  }
84
- catch (err) {
85
- this.error(`Failed to parse ${manifestPath}: ${err instanceof Error ? err.message : String(err)}`);
129
+ if (adapter.contextFile) {
130
+ const ctxPath = join(root, adapter.contextFile);
131
+ const before = existsSync(ctxPath) ? readFileSync(ctxPath, "utf8") : "";
132
+ const block = buildContextBlock({
133
+ projectName,
134
+ ...(presetId ? { preset: presetId } : {}),
135
+ ...(presetArticles ? { constitutionArticles: presetArticles } : {}),
136
+ });
137
+ if (!before.includes(block)) {
138
+ ctxChanged++;
139
+ if (!dry)
140
+ upsertContextFile(ctxPath, block);
141
+ }
86
142
  }
87
143
  }
88
- const constitutionMemory = join(raptorDir, "memory", "constitution.md");
89
- const constitutionRoot = join(raptorDir, "constitution.md");
90
- if (!existsSync(constitutionMemory) && existsSync(constitutionRoot)) {
91
- changes.push("COPY constitution.md memory/constitution.md (E02 upgrade)");
92
- if (!flags["dry-run"]) {
93
- const content = readFileSync(constitutionRoot, "utf-8");
94
- writeFileSync(constitutionMemory, content);
144
+ if (slashChanged > 0) {
145
+ changes.push(`REFRESH ${slashChanged} slash command file(s)`);
146
+ }
147
+ if (ctxChanged > 0) {
148
+ changes.push(`SYNC ${ctxChanged} agent context file(s)`);
149
+ }
150
+ const constPath = join(raptorDir, "constitution.md");
151
+ if (existsSync(constPath)) {
152
+ const src = readFileSync(constPath, "utf8");
153
+ const ex = extractCoreBlock(src);
154
+ if (ex?.isCanonical) {
155
+ }
156
+ else if (ex && !ex.isIntact) {
157
+ this.warn("Constitution CORE block was hand-edited (hash mismatch) — skipping. Run 'raptor repair constitution --confirm' first.");
158
+ skipped.push("constitution CORE (tampered)");
159
+ }
160
+ else {
161
+ const actor = currentActor("human");
162
+ if (!actor.email) {
163
+ this.warn("Constitution CORE is outdated but no `git config user.email` is set (C5) — skipping. Set it and re-run to upgrade the CORE.");
164
+ skipped.push("constitution CORE (no C5 approver)");
165
+ }
166
+ else {
167
+ changes.push(`UPGRADE constitution CORE → v${CORE_VERSION}`);
168
+ if (!dry) {
169
+ const memPath = join(raptorDir, "memory", "constitution.md");
170
+ backup(constPath);
171
+ if (existsSync(memPath))
172
+ backup(memPath);
173
+ const prevHash = hashString(src);
174
+ const next = rewriteCoreBlock(src);
175
+ writeFileSync(constPath, next);
176
+ mkdirSync(dirname(memPath), { recursive: true });
177
+ writeFileSync(memPath, next);
178
+ appendAuditEvent(join(raptorDir, "memory", "amendments.log"), {
179
+ event: "constitution.upgraded",
180
+ command: "raptor upgrade",
181
+ actor: { user: actor.user, via: actor.via },
182
+ article: "C4",
183
+ inputs: [
184
+ {
185
+ path: ".raptor/constitution.md",
186
+ hash: prevHash,
187
+ role: "constitution",
188
+ },
189
+ ],
190
+ outputs: [
191
+ {
192
+ path: ".raptor/constitution.md",
193
+ hash: hashString(next),
194
+ role: "constitution",
195
+ },
196
+ ],
197
+ meta: {
198
+ previous_version: ex?.version ?? null,
199
+ new_version: CORE_VERSION,
200
+ new_block_hash: coreHash(),
201
+ },
202
+ });
203
+ }
204
+ }
95
205
  }
96
206
  }
97
207
  const initOptionsPath = join(raptorDir, "init-options.json");
98
208
  if (!existsSync(initOptionsPath)) {
99
- changes.push("CREATE init-options.json (E06 upgrade)");
100
- if (!flags["dry-run"]) {
209
+ changes.push("CREATE init-options.json");
210
+ if (!dry) {
101
211
  writeFileSync(initOptionsPath, JSON.stringify({
102
- ai: "claude-code",
212
+ ai: initOpts?.ai ?? "claude-code",
103
213
  script_type: "sh",
104
214
  branch_numbering: "sequential",
105
- preset: "mobile-opinionated",
215
+ preset: presetId ?? "mobile-opinionated",
106
216
  no_git: false,
107
217
  created_at: new Date().toISOString(),
108
218
  }, null, 2) + "\n");
109
219
  }
110
220
  }
111
- if (existsSync(raptorYmlPath)) {
112
- try {
113
- const ymlContent = readFileSync(raptorYmlPath, "utf-8");
114
- if (/^agents:\s*\[\]/m.test(ymlContent)) {
115
- changes.push("REMOVE legacy agents: block from raptor.yml (agents now in agents.yml)");
116
- if (!flags["dry-run"]) {
117
- const cleaned = ymlContent.replace(/\nagents:\s*\[\]\n(?:#[^\n]*\n)*/, "\n# Agent configuration lives in agents.yml — do not add agents here.\n# Run 'raptor add-agent <key>' to configure agents.\n");
118
- writeFileSync(raptorYmlPath, cleaned);
119
- }
120
- }
221
+ const memC = join(raptorDir, "memory", "constitution.md");
222
+ if (!existsSync(memC) && existsSync(constPath)) {
223
+ changes.push("COPY constitution.md memory/constitution.md");
224
+ if (!dry) {
225
+ mkdirSync(dirname(memC), { recursive: true });
226
+ copyFileSync(constPath, memC);
121
227
  }
122
- catch {
228
+ }
229
+ const agentsYmlPath = join(raptorDir, "agents.yml");
230
+ if (!existsSync(agentsYmlPath)) {
231
+ changes.push("CREATE agents.yml");
232
+ if (!dry) {
233
+ writeFileSync(agentsYmlPath, generateAgentsYaml(agents.map((a) => ({
234
+ id: a.id,
235
+ kind: a.id,
236
+ capabilities: [...a.capabilities],
237
+ }))));
123
238
  }
124
239
  }
240
+ if (!dry) {
241
+ manifest.updated_at = new Date().toISOString();
242
+ writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
243
+ session?.discardIfEmpty();
244
+ }
125
245
  if (changes.length === 0) {
126
246
  this.log("✓ Project is up to date. No changes needed.");
247
+ for (const s of skipped)
248
+ this.log(` ⊘ skipped: ${s}`);
127
249
  return;
128
250
  }
129
- if (flags["dry-run"]) {
130
- this.log("\nDRY RUN — would apply the following changes:\n");
131
- for (const change of changes) {
132
- this.log(` • ${change}`);
133
- }
251
+ if (dry) {
252
+ this.log("\nDRY RUN — would apply:\n");
253
+ for (const c of changes)
254
+ this.log(` • ${c}`);
255
+ for (const s of skipped)
256
+ this.log(` ⊘ skipped: ${s}`);
134
257
  this.log("\nRe-run without --dry-run to apply.");
135
258
  }
136
259
  else {
137
- this.log("\n✓ Upgrade complete. Changes applied:\n");
138
- for (const change of changes) {
139
- this.log(` ✓ ${change}`);
260
+ this.log("\n✓ Upgrade complete:\n");
261
+ for (const c of changes)
262
+ this.log(` ✓ ${c}`);
263
+ for (const s of skipped)
264
+ this.log(` ⊘ skipped: ${s}`);
265
+ if (session && existsSync(session.dir)) {
266
+ this.log(`\n 💾 Previous versions backed up to ${session.dir}`);
267
+ }
268
+ }
269
+ }
270
+ resolveAgents(root, primaryAi) {
271
+ const cfg = loadAgentsConfig(root);
272
+ if (cfg?.agents?.length) {
273
+ const seen = new Set();
274
+ const out = [];
275
+ for (const a of cfg.agents) {
276
+ const adapter = getAgentByKind(a.kind ?? a.id);
277
+ if (adapter && !seen.has(adapter.id)) {
278
+ seen.add(adapter.id);
279
+ out.push(adapter);
280
+ }
140
281
  }
282
+ if (out.length)
283
+ return out;
141
284
  }
285
+ const primary = primaryAi ? getAgentByKind(primaryAi) : undefined;
286
+ const detected = detectInstalledAgents();
287
+ const out = primary
288
+ ? [primary, ...detected.filter((a) => a.id !== primary.id)]
289
+ : detected;
290
+ return out;
142
291
  }
143
292
  }
@@ -31,6 +31,12 @@ export function writeConfig(root, config) {
31
31
  const path = join(root, '.raptor', 'raptor.yml');
32
32
  writeFileSync(path, stringifyYaml(config));
33
33
  }
34
+ export function restampYmlVersion(raw, version) {
35
+ if (/^version:.*$/m.test(raw)) {
36
+ return raw.replace(/^version:.*$/m, `version: ${version}`);
37
+ }
38
+ return `version: ${version}\n${raw}`;
39
+ }
34
40
  export function readInitOptions(root) {
35
41
  const path = join(root, '.raptor', 'init-options.json');
36
42
  if (!existsSync(path))
@@ -0,0 +1,162 @@
1
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, statSync, writeFileSync, } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ export function findRaptorPackageRoot() {
4
+ let dir = dirname(new URL(import.meta.url).pathname);
5
+ for (let i = 0; i < 10; i++) {
6
+ if (existsSync(join(dir, "pnpm-workspace.yaml")) ||
7
+ existsSync(join(dir, "scripts", "bash"))) {
8
+ return dir;
9
+ }
10
+ const parent = dirname(dir);
11
+ if (parent === dir)
12
+ break;
13
+ dir = parent;
14
+ }
15
+ return null;
16
+ }
17
+ function walkFiles(dir, onFile) {
18
+ for (const entry of readdirSync(dir)) {
19
+ const abs = join(dir, entry);
20
+ if (statSync(abs).isDirectory())
21
+ walkFiles(abs, onFile);
22
+ else
23
+ onFile(abs);
24
+ }
25
+ }
26
+ export function collectFrameworkFiles(raptorDir) {
27
+ const pkgRoot = findRaptorPackageRoot();
28
+ const out = [];
29
+ if (!pkgRoot)
30
+ return out;
31
+ for (const { dir, ext } of [
32
+ { dir: "bash", ext: ".sh" },
33
+ { dir: "powershell", ext: ".ps1" },
34
+ ]) {
35
+ const s = join(pkgRoot, "scripts", dir);
36
+ if (!existsSync(s))
37
+ continue;
38
+ for (const f of readdirSync(s).filter((f) => f.endsWith(ext))) {
39
+ out.push({
40
+ src: join(s, f),
41
+ dest: join(raptorDir, "scripts", dir, f),
42
+ exec: ext === ".sh",
43
+ });
44
+ }
45
+ }
46
+ const sc = join(pkgRoot, "templates", "commands");
47
+ if (existsSync(sc)) {
48
+ for (const f of readdirSync(sc).filter((f) => f.endsWith(".md"))) {
49
+ out.push({ src: join(sc, f), dest: join(raptorDir, "templates", "commands", f) });
50
+ }
51
+ }
52
+ const st = join(pkgRoot, "templates");
53
+ if (existsSync(st)) {
54
+ for (const f of readdirSync(st).filter((f) => f.endsWith("-template.md"))) {
55
+ out.push({ src: join(st, f), dest: join(raptorDir, "templates", f) });
56
+ }
57
+ }
58
+ const se = join(pkgRoot, "extensions");
59
+ if (existsSync(se)) {
60
+ for (const extId of readdirSync(se)) {
61
+ const sed = join(se, extId);
62
+ if (!existsSync(join(sed, "extension.yml")))
63
+ continue;
64
+ walkFiles(sed, (abs) => out.push({
65
+ src: abs,
66
+ dest: join(raptorDir, "extensions", extId, relative(sed, abs)),
67
+ }));
68
+ }
69
+ }
70
+ return out;
71
+ }
72
+ export function copyDirRecursive(source, target) {
73
+ mkdirSync(target, { recursive: true });
74
+ for (const entry of readdirSync(source)) {
75
+ const src = join(source, entry);
76
+ const dest = join(target, entry);
77
+ if (statSync(src).isDirectory()) {
78
+ copyDirRecursive(src, dest);
79
+ }
80
+ else {
81
+ copyFileSync(src, dest);
82
+ }
83
+ }
84
+ }
85
+ export function installShellScripts(cmd, raptorDir, manifest) {
86
+ const pkgRoot = findRaptorPackageRoot();
87
+ if (!pkgRoot) {
88
+ cmd.warn("Could not locate Raptor package root — shell scripts not copied.");
89
+ return [];
90
+ }
91
+ const copied = [];
92
+ const summary = [];
93
+ const shells = [
94
+ { dir: "bash", ext: ".sh" },
95
+ { dir: "powershell", ext: ".ps1" },
96
+ ];
97
+ for (const shell of shells) {
98
+ const sourceDir = join(pkgRoot, "scripts", shell.dir);
99
+ const targetDir = join(raptorDir, "scripts", shell.dir);
100
+ if (!existsSync(sourceDir))
101
+ continue;
102
+ mkdirSync(targetDir, { recursive: true });
103
+ const files = readdirSync(sourceDir).filter((f) => f.endsWith(shell.ext));
104
+ for (const file of files) {
105
+ const dest = join(targetDir, file);
106
+ copyFileSync(join(sourceDir, file), dest);
107
+ if (shell.ext === ".sh")
108
+ chmodSync(dest, 0o755);
109
+ copied.push(`scripts/${shell.dir}/${file}`);
110
+ }
111
+ if (files.length > 0)
112
+ summary.push(`${files.length} ${shell.dir}`);
113
+ }
114
+ manifest.scripts_installed = copied;
115
+ writeFileSync(join(raptorDir, "raptor.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
116
+ if (summary.length > 0)
117
+ cmd.log(` Scripts: ${summary.join(", ")}`);
118
+ return copied;
119
+ }
120
+ export function installTemplates(raptorDir) {
121
+ const pkgRoot = findRaptorPackageRoot();
122
+ if (!pkgRoot)
123
+ return;
124
+ const srcTemplates = join(pkgRoot, "templates");
125
+ if (!existsSync(srcTemplates))
126
+ return;
127
+ const srcCommands = join(srcTemplates, "commands");
128
+ if (existsSync(srcCommands)) {
129
+ const destCommands = join(raptorDir, "templates", "commands");
130
+ mkdirSync(destCommands, { recursive: true });
131
+ for (const f of readdirSync(srcCommands).filter((f) => f.endsWith(".md"))) {
132
+ copyFileSync(join(srcCommands, f), join(destCommands, f));
133
+ }
134
+ }
135
+ const destTemplates = join(raptorDir, "templates");
136
+ mkdirSync(destTemplates, { recursive: true });
137
+ for (const f of readdirSync(srcTemplates).filter((f) => f.endsWith("-template.md"))) {
138
+ copyFileSync(join(srcTemplates, f), join(destTemplates, f));
139
+ }
140
+ }
141
+ export function installBundledExtensions(cmd, raptorDir, manifest) {
142
+ const pkgRoot = findRaptorPackageRoot();
143
+ if (!pkgRoot)
144
+ return [];
145
+ const sourceExtRoot = join(pkgRoot, "extensions");
146
+ if (!existsSync(sourceExtRoot))
147
+ return [];
148
+ const registered = [];
149
+ for (const extId of readdirSync(sourceExtRoot)) {
150
+ const sourceExt = join(sourceExtRoot, extId);
151
+ if (!existsSync(join(sourceExt, "extension.yml")))
152
+ continue;
153
+ copyDirRecursive(sourceExt, join(raptorDir, "extensions", extId));
154
+ registered.push(extId);
155
+ }
156
+ if (registered.length === 0)
157
+ return [];
158
+ manifest.extensions_registered = registered;
159
+ writeFileSync(join(raptorDir, "raptor.manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
160
+ cmd.log(` Ext: ${registered.length} bundled (${registered.join(", ")})`);
161
+ return registered;
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "raptor-aios",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Raptor — Spec-Driven Development (SDD) CLI for modern mobile apps. Constitutional gates, audit trail, real verification (a11y/perf/stores/OS matrix), and AI-agent slash commands.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@ const CLI = join(ROOT, "packages", "cli");
29
29
  const CORE = join(ROOT, "packages", "core");
30
30
  const OUT = join(ROOT, "build", "npm");
31
31
 
32
- const VERSION = "0.6.1";
32
+ const VERSION = "0.6.3";
33
33
 
34
34
  function log(msg) {
35
35
  process.stdout.write(` ${msg}\n`);