@luizsantiago/spec-guardrails 3.1.11 → 3.2.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.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  | **Solution** | One kit, two deliberate modes: **Process** (Node only) for a flexible spec-driven workflow; **Brakes** (Node + Python) for the **full product** — structural gates that exit non-zero when paperwork or evidence is missing. You approve specs/tasks in both. |
12
12
  | **Result** | Traceable `.specs/` memory, fewer fake finishes, cheaper turns (~70% less skill text on planning). Choose Process for light ceremony; add Python when you want the [Guarantees matrix](#guarantees-matrix) enforced automatically. |
13
13
 
14
- npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.1.x**
14
+ npm: [`@luizsantiago/spec-guardrails`](https://www.npmjs.com/package/@luizsantiago/spec-guardrails) **3.2.x**
15
15
 
16
16
  ---
17
17
 
@@ -41,11 +41,16 @@ npx @luizsantiago/spec-guardrails install
41
41
 
42
42
  Re-run `install` anytime to refresh skills; your `.specs/` decisions and `STATE.md` are kept.
43
43
 
44
- ### Optional Atlas companions (Lego)
44
+ ### Governance focus (3.2+)
45
45
 
46
- Spec Guardrails is a **complete product alone**. Optional **Atlas** packages add domain specialization (Path Domains, catalogs). The first is [**Tech Atlas**](https://github.com/luizssantiago92/tech-atlas) (`@luizsantiago/tech-atlas`) — tech engineering context routing.
46
+ Spec Guardrails is a **single product**: governance, evidence, verification, and controlled execution for agentic software development not a bundle of companion packages.
47
47
 
48
- When paired, install Guardrails first, then the Atlas. Re-running Guardrails `install` **preserves** companion assets: Atlas skills/catalog, `.specs/atlas/`, mirrored `validate_layer_routing.py`, and `.specs/desks/` if present. Contract: [Companion: Tech Atlas](docs/guide/Companion-tech-atlas.md).
48
+ | Capability | What it does |
49
+ | --- | --- |
50
+ | **Artifact gates** | Structural quality for spec, tasks, and cross-artifact consistency — with **blocking / warning / info** severity |
51
+ | **Parallel waves** | `loop-plan` computes safe parallel groups; `workspace-prepare` isolates tasks in git worktrees |
52
+ | **Execution policy** | Budgets, path scope, and escalation rules in `.specs/config.yaml` — consult via `execution-policy` |
53
+ | **Independent verify** | Fresh-context verification with evidence-or-zero (`validate-state`) |
49
54
 
50
55
  | Need | Command |
51
56
  | --- | --- |
@@ -196,6 +201,7 @@ Full reference: **[Gates](docs/guide/gates.md)** · [Guarantees matrix](docs/gui
196
201
  | [Skills and hub](docs/guide/skills-and-hub.md) | What each skill file does |
197
202
  | [Gates](docs/guide/gates.md) | How each gate works |
198
203
  | [Platform parity](docs/guide/Platform-parity.md) | Shipped adapters (Cursor, Claude, Copilot, Codex) — core works with any agent |
204
+ | [Restart PRD seed](docs/guide/Restart-prd-seed.md) | Clean-project PRD template for a single-package restart |
199
205
  | [FAQ](docs/guide/FAQ.md) | Common questions |
200
206
  | [Changelog](docs/CHANGELOG.md) | Full version history |
201
207
 
@@ -211,7 +217,8 @@ npx @luizsantiago/spec-guardrails install
211
217
 
212
218
  | Version | What you gain |
213
219
  | --- | --- |
214
- | **3.1.x** | Copilot/Codex/AGENTS.md adapters; doctor Process + Brakes scores; unified execution contracts; `validate-traceability` / `validate-quick`; `classify-change` / `feature-status` |
220
+ | **3.2.x** | Single-package focus; artifact gate severity labels; git worktree isolation CLI; execution policy (budget/scope/escalation) |
221
+ | **3.1.x** | Copilot/Codex/AGENTS.md adapters; doctor Process + Brakes scores; `validate-traceability` / `validate-quick`; `classify-change` / `feature-status` |
215
222
  | **3.0.x** | Final name Spec Guardrails; `.specs/guardrails/`; no dual-path ([Migration](docs/guide/Migration.md)) |
216
223
  | **2.2.x** | Seatbelt-era paths & markers; `doctor` Execute hints; docs split from README |
217
224
  | **2.1.x** | `loop-plan` + parallel `/loop` waves |
package/index.js CHANGED
@@ -8,10 +8,26 @@ import { classifyChange, formatClassifyChange } from "./lib/classify-change.js";
8
8
  import { PACKAGE_VERSION, CLI_NAME } from "./lib/constants.js";
9
9
  import { phaseContext } from "./lib/config.js";
10
10
  import { doctor } from "./lib/doctor.js";
11
+ import {
12
+ formatPolicyStatus,
13
+ loadExecutionPolicy,
14
+ loadPolicyState,
15
+ recordAgentRun,
16
+ recordTaskRetry,
17
+ resolvePathCheck,
18
+ savePolicyState,
19
+ } from "./lib/execution-policy.js";
11
20
  import { featureInit } from "./lib/feature.js";
12
21
  import { featureStatus, formatFeatureStatus } from "./lib/feature-status.js";
13
22
  import { GATE_COMMANDS, AUX_COMMANDS, runGate, runGuardrailsScript } from "./lib/gates.js";
14
23
  import { install } from "./lib/install.js";
24
+ import {
25
+ cleanupWorkspaces,
26
+ formatWorkspaceList,
27
+ formatWorkspaceResults,
28
+ listWorkspaces,
29
+ prepareWorkspaces,
30
+ } from "./lib/workspace-isolation.js";
15
31
  import {
16
32
  initProjectConfig,
17
33
  listPresets,
@@ -50,10 +66,28 @@ Commands:
50
66
  [--json] Machine-readable output
51
67
  phase-context <phase> Print .specs/config.yaml context + rules for a phase
52
68
  doctor [path] Audit guardrails readiness (score + next actions)
53
- When Atlas companions are installed, also probes their
54
- gates, rules, and PROJECT.md registry via INDEX.json
55
69
  [--json] Machine-readable output
56
70
  [--no-suggest] Hide per-check remediation hints
71
+ workspace-prepare <feature> Create isolated git worktrees for parallel tasks
72
+ --tasks T1,T2 Task ids to isolate (required)
73
+ [--base-ref HEAD] Base ref for new worktrees
74
+ [--json] Machine-readable output
75
+ workspace-cleanup <feature> Remove isolated worktrees for a feature
76
+ [--tasks T1,T2] Limit cleanup to specific tasks
77
+ [--force] Force-remove dirty worktrees (recovery after worker FAIL)
78
+ [--json] Machine-readable output
79
+ workspace-list <feature> List isolated worktrees for a feature
80
+ [--json] Machine-readable output
81
+ execution-policy status Show configured budgets, scope, and runtime counters
82
+ [--json] Machine-readable output
83
+ execution-policy check-path <path> Check whether a relative path is allowed by scope policy
84
+ [--json] Machine-readable output
85
+ execution-policy record-retry <task> Increment retry counter for a task id (blocks at limit)
86
+ execution-policy record-run Increment agent-run counter (blocks at budget)
87
+ memory-index rebuild Rebuild SQLite memory index from .specs/ artifacts
88
+ memory-query --from <id> Bounded context package from the knowledge graph
89
+ [--depth N] Traversal depth (default 2)
90
+ [--json] Machine-readable output
57
91
  validate-spec [spec.md|feature] Closure gate for a feature spec
58
92
  analyze-artifacts [feature] Cross-artifact consistency before task approval
59
93
  validate-tasks [tasks.md|feature] Granularity gate for a task breakdown
@@ -63,7 +97,7 @@ Commands:
63
97
  validate-quick [quick-folder] Quick-mode TASK.md / SUMMARY.md structural gate
64
98
  validate-state [feature] Completion gate before declaring a feature done
65
99
  check-commit --message "<msg>" Conventional Commits gate
66
- lessons <add|list|penalize|prune|status> Lessons engine
100
+ lessons <add|list|penalize|prune|promote|graduate|status> Lessons engine
67
101
  --help Show this message
68
102
  --version Print the package version
69
103
  `;
@@ -323,6 +357,192 @@ if (command === "--version" || command === "-v" || command === "version") {
323
357
  console.error(`❌ ${err.message}`);
324
358
  process.exit(1);
325
359
  }
360
+ } else if (command === "workspace-prepare") {
361
+ try {
362
+ let json = false;
363
+ let baseRef = "HEAD";
364
+ let tasksRaw = "";
365
+ const positional = [];
366
+
367
+ for (let i = 0; i < args.length; i++) {
368
+ const arg = args[i];
369
+ if (arg === "--json") {
370
+ json = true;
371
+ } else if (arg === "--base-ref") {
372
+ baseRef = args[++i];
373
+ if (!baseRef) {
374
+ throw new Error("--base-ref requires a git ref");
375
+ }
376
+ } else if (arg === "--tasks") {
377
+ tasksRaw = args[++i] ?? "";
378
+ if (!tasksRaw) {
379
+ throw new Error("--tasks requires a comma-separated list (e.g. T1,T2)");
380
+ }
381
+ } else {
382
+ positional.push(arg);
383
+ }
384
+ }
385
+
386
+ const featureId = positional[0];
387
+ if (!featureId) {
388
+ throw new Error("Usage: workspace-prepare <feature> --tasks T1,T2");
389
+ }
390
+
391
+ const taskIds = tasksRaw.split(",").map((item) => item.trim()).filter(Boolean);
392
+ const results = await prepareWorkspaces(process.cwd(), { featureId, taskIds, baseRef });
393
+ process.stdout.write(formatWorkspaceResults(results, { json }));
394
+
395
+ if (results.some((item) => item.status === "failed")) {
396
+ process.exit(1);
397
+ }
398
+ } catch (err) {
399
+ console.error(`❌ ${err.message}`);
400
+ process.exit(1);
401
+ }
402
+ } else if (command === "workspace-cleanup") {
403
+ try {
404
+ let json = false;
405
+ let force = false;
406
+ let tasksRaw = "";
407
+ const positional = [];
408
+
409
+ for (let i = 0; i < args.length; i++) {
410
+ const arg = args[i];
411
+ if (arg === "--json") {
412
+ json = true;
413
+ } else if (arg === "--force") {
414
+ force = true;
415
+ } else if (arg === "--tasks") {
416
+ tasksRaw = args[++i] ?? "";
417
+ } else {
418
+ positional.push(arg);
419
+ }
420
+ }
421
+
422
+ const featureId = positional[0];
423
+ if (!featureId) {
424
+ throw new Error("Usage: workspace-cleanup <feature> [--tasks T1,T2] [--force]");
425
+ }
426
+
427
+ const taskIds = tasksRaw
428
+ ? tasksRaw.split(",").map((item) => item.trim()).filter(Boolean)
429
+ : undefined;
430
+ const results = await cleanupWorkspaces(process.cwd(), { featureId, taskIds, force });
431
+ process.stdout.write(formatWorkspaceResults(results, { json }));
432
+
433
+ if (results.some((item) => item.status === "failed")) {
434
+ process.exit(1);
435
+ }
436
+ } catch (err) {
437
+ console.error(`❌ ${err.message}`);
438
+ process.exit(1);
439
+ }
440
+ } else if (command === "workspace-list") {
441
+ try {
442
+ let json = false;
443
+ const positional = [];
444
+
445
+ for (const arg of args) {
446
+ if (arg === "--json") {
447
+ json = true;
448
+ } else {
449
+ positional.push(arg);
450
+ }
451
+ }
452
+
453
+ const featureId = positional[0];
454
+ if (!featureId) {
455
+ throw new Error("Usage: workspace-list <feature> [--json]");
456
+ }
457
+
458
+ const workspaces = await listWorkspaces(process.cwd(), featureId);
459
+ process.stdout.write(formatWorkspaceList(workspaces, { json, featureId }));
460
+ } catch (err) {
461
+ console.error(`❌ ${err.message}`);
462
+ process.exit(1);
463
+ }
464
+ } else if (command === "execution-policy") {
465
+ try {
466
+ const sub = args[0];
467
+ let json = false;
468
+ const rest = [];
469
+
470
+ for (let i = 1; i < args.length; i++) {
471
+ if (args[i] === "--json") {
472
+ json = true;
473
+ } else {
474
+ rest.push(args[i]);
475
+ }
476
+ }
477
+
478
+ const cwd = process.cwd();
479
+ const policy = await loadExecutionPolicy(cwd);
480
+ const state = await loadPolicyState(cwd);
481
+
482
+ if (sub === "status") {
483
+ process.stdout.write(formatPolicyStatus(policy, state, { json }));
484
+ } else if (sub === "check-path") {
485
+ const relativePath = rest[0];
486
+ if (!relativePath) {
487
+ throw new Error("Usage: execution-policy check-path <relative-path>");
488
+ }
489
+ const result = resolvePathCheck(relativePath, policy);
490
+ if (json) {
491
+ console.log(JSON.stringify({ path: relativePath, ...result }, null, 2));
492
+ } else {
493
+ const label = result.allowed
494
+ ? "allowed"
495
+ : result.severity === "warning"
496
+ ? "blocked (warn)"
497
+ : "blocked";
498
+ console.log(`${relativePath}: ${label} (${result.reason})`);
499
+ }
500
+ if (result.exitCode !== 0) {
501
+ process.exit(result.exitCode);
502
+ }
503
+ } else if (sub === "record-retry") {
504
+ const taskId = rest[0];
505
+ if (!taskId) {
506
+ throw new Error("Usage: execution-policy record-retry <task-id>");
507
+ }
508
+ const recorded = recordTaskRetry(state, taskId, policy);
509
+ if (!recorded.ok) {
510
+ console.error(`❌ ${recorded.message}`);
511
+ process.exit(1);
512
+ }
513
+ await savePolicyState(cwd, recorded.state);
514
+ if (json) {
515
+ console.log(
516
+ JSON.stringify({ taskId, retries: recorded.retries, state: recorded.state }, null, 2),
517
+ );
518
+ } else {
519
+ console.log(`Recorded retry for ${taskId}: ${recorded.retries}`);
520
+ }
521
+ } else if (sub === "record-run") {
522
+ const recorded = recordAgentRun(state, policy);
523
+ if (!recorded.ok) {
524
+ console.error(`❌ ${recorded.message}`);
525
+ process.exit(1);
526
+ }
527
+ await savePolicyState(cwd, recorded.state);
528
+ if (json) {
529
+ console.log(
530
+ JSON.stringify({ agent_runs: recorded.state.agent_runs, state: recorded.state }, null, 2),
531
+ );
532
+ } else {
533
+ console.log(
534
+ `Recorded agent run: ${recorded.state.agent_runs}/${policy.budget.max_agent_runs}`,
535
+ );
536
+ }
537
+ } else {
538
+ throw new Error(
539
+ "Usage: execution-policy status | check-path <path> | record-retry <task> | record-run",
540
+ );
541
+ }
542
+ } catch (err) {
543
+ console.error(`❌ ${err.message}`);
544
+ process.exit(1);
545
+ }
326
546
  } else if (command === "classify-change") {
327
547
  try {
328
548
  let json = false;
@@ -375,7 +595,8 @@ if (command === "--version" || command === "-v" || command === "version") {
375
595
  console.error(`❌ ${err.message}`);
376
596
  process.exit(1);
377
597
  }
378
- } else if (AUX_COMMANDS.includes(command)) { try {
598
+ } else if (AUX_COMMANDS.includes(command)) {
599
+ try {
379
600
  const code = await runGuardrailsScript(command, args);
380
601
  process.exit(code);
381
602
  } catch (err) {
@@ -0,0 +1,128 @@
1
+ import { injectAgentsMd } from "./agents-md.js";
2
+ import { injectCodexAgents } from "./codex-agents.js";
3
+ import { injectCopilotInstructions } from "./copilot-instructions.js";
4
+ import { injectClaudeMd } from "./claude-md.js";
5
+ import { injectCursorRules } from "./cursorrules.js";
6
+
7
+ /**
8
+ * @typedef {{
9
+ * id: string,
10
+ * label: string,
11
+ * skillsDir: string | null,
12
+ * entryFiles: string[],
13
+ * capabilities: {
14
+ * supports_hooks: boolean,
15
+ * supports_commands: boolean,
16
+ * supports_rules: boolean,
17
+ * supports_skills: boolean,
18
+ * },
19
+ * install: (cwd: string) => Promise<void>,
20
+ * }} PlatformAdapter
21
+ */
22
+
23
+ /** @type {PlatformAdapter[]} */
24
+ export const ADAPTER_REGISTRY = [
25
+ {
26
+ id: "cursor",
27
+ label: "Cursor",
28
+ skillsDir: ".cursor/skills",
29
+ entryFiles: [".cursorrules", ".cursor/rules/engineering-baseline.mdc"],
30
+ capabilities: {
31
+ supports_hooks: true,
32
+ supports_commands: true,
33
+ supports_rules: true,
34
+ supports_skills: true,
35
+ },
36
+ install: injectCursorRules,
37
+ },
38
+ {
39
+ id: "claude",
40
+ label: "Claude Code",
41
+ skillsDir: ".claude/skills",
42
+ entryFiles: [".claude/CLAUDE.md"],
43
+ capabilities: {
44
+ supports_hooks: false,
45
+ supports_commands: true,
46
+ supports_rules: false,
47
+ supports_skills: true,
48
+ },
49
+ install: injectClaudeMd,
50
+ },
51
+ {
52
+ id: "copilot",
53
+ label: "GitHub Copilot",
54
+ skillsDir: ".github/skills",
55
+ entryFiles: [".github/copilot-instructions.md"],
56
+ capabilities: {
57
+ supports_hooks: false,
58
+ supports_commands: false,
59
+ supports_rules: false,
60
+ supports_skills: true,
61
+ },
62
+ install: injectCopilotInstructions,
63
+ },
64
+ {
65
+ id: "codex",
66
+ label: "OpenAI Codex",
67
+ skillsDir: ".codex/skills",
68
+ entryFiles: [".codex/AGENTS.md"],
69
+ capabilities: {
70
+ supports_hooks: false,
71
+ supports_commands: false,
72
+ supports_rules: false,
73
+ supports_skills: true,
74
+ },
75
+ install: injectCodexAgents,
76
+ },
77
+ {
78
+ id: "agents-md",
79
+ label: "AGENTS.md (open standard)",
80
+ skillsDir: null,
81
+ entryFiles: ["AGENTS.md"],
82
+ capabilities: {
83
+ supports_hooks: false,
84
+ supports_commands: false,
85
+ supports_rules: false,
86
+ supports_skills: false,
87
+ },
88
+ install: injectAgentsMd,
89
+ },
90
+ ];
91
+
92
+ /**
93
+ * @param {string} id
94
+ * @returns {PlatformAdapter | undefined}
95
+ */
96
+ export function getAdapter(id) {
97
+ return ADAPTER_REGISTRY.find((adapter) => adapter.id === id);
98
+ }
99
+
100
+ /** @returns {PlatformAdapter[]} */
101
+ export function listAdapters() {
102
+ return [...ADAPTER_REGISTRY];
103
+ }
104
+
105
+ /**
106
+ * @param {keyof PlatformAdapter["capabilities"]} capability
107
+ * @returns {PlatformAdapter[]}
108
+ */
109
+ export function getAdaptersWithCapability(capability) {
110
+ return ADAPTER_REGISTRY.filter((adapter) => adapter.capabilities[capability]);
111
+ }
112
+
113
+ /**
114
+ * @param {string} cwd
115
+ * @param {PlatformAdapter} [adapter]
116
+ */
117
+ export async function installAdapter(cwd, adapter) {
118
+ await adapter.install(cwd);
119
+ }
120
+
121
+ /**
122
+ * Install all registered platform adapters.
123
+ *
124
+ * @param {string} cwd
125
+ */
126
+ export async function installAllAdapters(cwd) {
127
+ await Promise.all(ADAPTER_REGISTRY.map((adapter) => installAdapter(cwd, adapter)));
128
+ }
package/lib/adapters.js CHANGED
@@ -1,17 +1,11 @@
1
- import { injectAgentsMd } from "./agents-md.js";
2
- import { injectCodexAgents } from "./codex-agents.js";
3
- import { injectCopilotInstructions } from "./copilot-instructions.js";
1
+ import { installAllAdapters } from "./adapter-registry.js";
4
2
 
5
3
  /**
6
4
  * Install shipped platform adapter entry files (Copilot, Codex, AGENTS.md).
7
- * Cursor and Claude adapters are injected separately in install.js.
5
+ * Cursor and Claude adapters are injected via the same registry during install.
8
6
  *
9
7
  * @param {string} cwd
10
8
  */
11
9
  export async function installPlatformAdapters(cwd) {
12
- return Promise.all([
13
- injectCopilotInstructions(cwd),
14
- injectAgentsMd(cwd),
15
- injectCodexAgents(cwd),
16
- ]);
10
+ return installAllAdapters(cwd);
17
11
  }
package/lib/archive.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  import { ensureDir, readFileSafe, writeFileSafe } from "./fs-utils.js";
10
10
  import { runGate } from "./gates.js";
11
11
  import { assertSafeDomainSlug } from "./slug-utils.js";
12
+ import { isPassVerdict } from "./validation-verdict.js";
12
13
  import {
13
14
  featureDir,
14
15
  readFeatureArtifact,
@@ -23,15 +24,6 @@ Track milestones and archived features.
23
24
 
24
25
  `;
25
26
 
26
- /**
27
- * @param {string} text
28
- * @returns {boolean}
29
- */
30
- function validationPassed(text) {
31
- const visible = text.replace(/<!--[\s\S]*?-->/g, "");
32
- return /\b(?:PASS|PASSED)\b/.test(visible);
33
- }
34
-
35
27
  /**
36
28
  * @param {string} cwd
37
29
  * @param {string} featureId
@@ -150,7 +142,7 @@ export async function archiveFeature(featureArg, options = {}) {
150
142
  },
151
143
  );
152
144
 
153
- if (!validationPassed(validationText)) {
145
+ if (!isPassVerdict(validationText)) {
154
146
  throw new Error(
155
147
  `validation.md for ${featureId} does not contain PASS/PASSED. Run validate-state first.`,
156
148
  );
package/lib/assets.js CHANGED
@@ -55,8 +55,9 @@ export function resolveInstallSource(repoUrl) {
55
55
  /**
56
56
  * @param {string} remotePath
57
57
  * @param {string} destPath
58
+ * @param {{ boundary?: string }} [options]
58
59
  */
59
- export async function copyPackagedAsset(remotePath, destPath) {
60
+ export async function copyPackagedAsset(remotePath, destPath, options = {}) {
60
61
  const source = packagedAssetPath(remotePath);
61
62
 
62
63
  try {
@@ -71,7 +72,7 @@ export async function copyPackagedAsset(remotePath, destPath) {
71
72
  throw err;
72
73
  }
73
74
 
74
- await assertSafeWriteTarget(destPath);
75
+ await assertSafeWriteTarget(destPath, options);
75
76
 
76
77
  try {
77
78
  await fs.copyFile(source, destPath);
@@ -91,7 +92,7 @@ export async function copyPackagedAsset(remotePath, destPath) {
91
92
  * repo with no override (kept for the remote path; the default install no
92
93
  * longer uses it).
93
94
  *
94
- * @param {{ remotePath: string, destPath: string, repoUrl?: string, state: { warned: boolean }, log: (msg: string) => void }} params
95
+ * @param {{ remotePath: string, destPath: string, repoUrl?: string, state: { warned: boolean }, log: (msg: string) => void, boundary?: string }} params
95
96
  */
96
97
  export async function downloadRemoteAsset({
97
98
  remotePath,
@@ -99,9 +100,10 @@ export async function downloadRemoteAsset({
99
100
  repoUrl,
100
101
  state,
101
102
  log,
103
+ boundary,
102
104
  }) {
103
105
  try {
104
- await downloadToFile(resolveAssetUrl(remotePath, repoUrl), destPath);
106
+ await downloadToFile(resolveAssetUrl(remotePath, repoUrl), destPath, { boundary });
105
107
  return;
106
108
  } catch (err) {
107
109
  const missingPinnedAsset =
@@ -123,15 +125,16 @@ export async function downloadRemoteAsset({
123
125
  await downloadToFile(
124
126
  resolveAssetUrl(remotePath, FALLBACK_REPO_URL),
125
127
  destPath,
128
+ { boundary },
126
129
  );
127
130
  }
128
131
 
129
132
  /**
130
- * @param {{ remotePath: string, destPath: string, source: ReturnType<typeof resolveInstallSource>, state: { warned: boolean }, log: (msg: string) => void }} params
133
+ * @param {{ remotePath: string, destPath: string, source: ReturnType<typeof resolveInstallSource>, state: { warned: boolean }, log: (msg: string) => void, boundary?: string }} params
131
134
  */
132
- export async function installAsset({ remotePath, destPath, source, state, log }) {
135
+ export async function installAsset({ remotePath, destPath, source, state, log, boundary }) {
133
136
  if (source.mode === "package") {
134
- await copyPackagedAsset(remotePath, destPath);
137
+ await copyPackagedAsset(remotePath, destPath, { boundary });
135
138
  return;
136
139
  }
137
140
 
@@ -141,5 +144,6 @@ export async function installAsset({ remotePath, destPath, source, state, log })
141
144
  repoUrl: source.repoUrl,
142
145
  state,
143
146
  log,
147
+ boundary,
144
148
  });
145
149
  }
package/lib/constants.js CHANGED
@@ -108,6 +108,8 @@ export const SCRIPT_ASSETS = [
108
108
  { file: "check_commit.py", remotePath: "scripts/check_commit.py" },
109
109
  { file: "lessons.py", remotePath: "scripts/lessons.py" },
110
110
  { file: "loop_plan.py", remotePath: "scripts/loop_plan.py" },
111
+ { file: "memory_index.py", remotePath: "scripts/memory_index.py" },
112
+ { file: "memory_query.py", remotePath: "scripts/memory_query.py" },
111
113
  ];
112
114
 
113
115
  /** @type {{ file: string, remotePath: string }[]} */
package/lib/doctor.js CHANGED
@@ -8,11 +8,6 @@ import {
8
8
  NPX,
9
9
  SKILL_DIRS,
10
10
  } from "./constants.js";
11
- import { listInstalledCompanions } from "./companions.js";
12
- import {
13
- runCompanionDoctorChecks,
14
- summarizeCompanionChecks,
15
- } from "./companion-doctor.js";
16
11
  import { resolvePython, resolveScriptsDir } from "./gates.js";
17
12
  import { readFileSafe } from "./fs-utils.js";
18
13
  import { listFeatureIds, readActiveFeatureFromState } from "./specs-utils.js";
@@ -203,21 +198,6 @@ export async function runDoctorChecks(cwd) {
203
198
  suggest: NPX("project-init"),
204
199
  });
205
200
 
206
- const companionReport = await runCompanionDoctorChecks(cwd);
207
- if (companionReport.checks.length > 0) {
208
- checks.push(...companionReport.checks);
209
- } else {
210
- checks.push({
211
- id: "atlas-companions",
212
- label: "Atlas companions registry (.specs/companions/INDEX.json)",
213
- weight: 5,
214
- pass: false,
215
- optional: true,
216
- suggest:
217
- "Optional: npx @luizsantiago/tech-atlas install (or another Atlas package)",
218
- });
219
- }
220
-
221
201
  const activeFeature = await readActiveFeature(cwd);
222
202
  let activeFeatureOk = true;
223
203
  let activeFeatureSuggest;
@@ -399,45 +379,16 @@ export async function doctor(cwd, options = {}) {
399
379
  const executeHint = await resolveExecuteHint(cwd, activeFeature);
400
380
  const pythonCheck = checks.find((check) => check.id === "python");
401
381
  const pythonMissing = pythonCheck ? !pythonCheck.pass : false;
402
- const companions = await listInstalledCompanions(cwd);
403
- const companionChecks = checks.filter((check) => check.id.startsWith("atlas-"));
404
- const companionSummary = summarizeCompanionChecks(companionChecks);
405
- const stackPaired = companions.length > 0 && companionChecks.some((c) => !c.optional);
406
382
 
407
383
  if (options.json) {
408
384
  console.log(
409
385
  JSON.stringify(
410
- {
411
- score,
412
- modes,
413
- checks,
414
- suggestions,
415
- executeHint,
416
- pythonMissing,
417
- stack: {
418
- paired: stackPaired,
419
- companions: companions.map((c) => ({
420
- id: c.id,
421
- npm: c.npm,
422
- version: c.version,
423
- displayName: c.displayName,
424
- })),
425
- atlasReady: companionSummary.ready,
426
- },
427
- },
386
+ { score, modes, checks, suggestions, executeHint, pythonMissing },
428
387
  null,
429
388
  2,
430
389
  ),
431
390
  );
432
- return {
433
- score,
434
- modes,
435
- checks,
436
- suggestions,
437
- executeHint,
438
- pythonMissing,
439
- stack: { paired: stackPaired, companions, atlasReady: companionSummary.ready },
440
- };
391
+ return { score, modes, checks, suggestions, executeHint, pythonMissing };
441
392
  }
442
393
 
443
394
  if (pythonMissing) {
@@ -456,13 +407,6 @@ export async function doctor(cwd, options = {}) {
456
407
  console.log(` Brakes: ${modes.brakes.score}/100${brakesHint}\n`);
457
408
  console.log(`Guardrails Ready: ${score}/100\n`);
458
409
 
459
- if (companions.length > 0) {
460
- const stackLabel = stackPaired ? "paired Lego stack" : "solo + Atlas companions";
461
- console.log(
462
- `Atlas stack: ${stackLabel} — ${companions.length} companion(s), ${companionSummary.passed}/${companionSummary.total} checks passed\n`,
463
- );
464
- }
465
-
466
410
  for (const check of checks) {
467
411
  const mark = check.pass ? "✓" : "✗";
468
412
  const optional = check.optional ? " (optional)" : "";
@@ -483,13 +427,5 @@ export async function doctor(cwd, options = {}) {
483
427
  console.log(`\nExecute hint:\n → ${executeHint}`);
484
428
  }
485
429
 
486
- return {
487
- score,
488
- modes,
489
- checks,
490
- suggestions,
491
- executeHint,
492
- pythonMissing,
493
- stack: { paired: stackPaired, companions, atlasReady: companionSummary.ready },
494
- };
430
+ return { score, modes, checks, suggestions, executeHint, pythonMissing };
495
431
  }