raptor-aios 0.6.1 → 0.6.2
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 +16 -1
- package/dist/_core/dist/agents/index.js +1 -1
- package/dist/_core/dist/agents/slash-materializer.js +17 -11
- package/dist/_core/package.json +1 -1
- package/dist/commands/init.js +6 -113
- package/dist/commands/upgrade.js +231 -82
- package/dist/shared/project.js +6 -0
- package/dist/shared/scaffold.js +162 -0
- package/package.json +1 -1
- package/scripts/prepare-npm.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,11 +3,26 @@
|
|
|
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.2] - 2026-06-06
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- **`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`:
|
|
11
|
+
- **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).
|
|
12
|
+
- **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).
|
|
13
|
+
- **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).
|
|
14
|
+
- **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.
|
|
15
|
+
|
|
16
|
+
### Internal
|
|
17
|
+
|
|
18
|
+
- 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`.
|
|
19
|
+
|
|
6
20
|
## [0.6.1] - 2026-06-05
|
|
7
21
|
|
|
8
22
|
### Changed
|
|
9
23
|
|
|
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).
|
|
24
|
+
- 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).
|
|
25
|
+
- **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
26
|
|
|
12
27
|
## [0.6.0] - 2026-06-05
|
|
13
28
|
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
72
|
-
writeFileSync(filePath, content);
|
|
73
|
-
materialized.push({
|
|
70
|
+
planned.push({
|
|
74
71
|
command,
|
|
75
|
-
path:
|
|
72
|
+
path: join(commandDir, fileName),
|
|
73
|
+
content,
|
|
76
74
|
hash: hashString(content),
|
|
77
75
|
});
|
|
78
76
|
}
|
|
79
|
-
return {
|
|
80
|
-
|
|
81
|
-
|
|
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
|
}
|
package/dist/_core/package.json
CHANGED
package/dist/commands/init.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
import { BaseCommand } from "../base-command.js";
|
|
3
|
-
import { chmodSync,
|
|
4
|
-
import { basename,
|
|
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
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
}
|
package/dist/commands/upgrade.js
CHANGED
|
@@ -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 = "
|
|
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
|
|
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
|
|
21
|
-
const
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.warn(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
100
|
-
if (!
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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 (
|
|
130
|
-
this.log("\nDRY RUN — would apply
|
|
131
|
-
for (const
|
|
132
|
-
this.log(` • ${
|
|
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
|
|
138
|
-
for (const
|
|
139
|
-
this.log(` ✓ ${
|
|
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
|
}
|
package/dist/shared/project.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.6.2",
|
|
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": {
|
package/scripts/prepare-npm.mjs
CHANGED