instar 1.0.13 → 1.1.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 +1 -1
- package/dist/cli.js +52 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +156 -64
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +68 -4
- package/dist/commands/server.js.map +1 -1
- package/dist/commands/setup.d.ts +3 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +67 -28
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/worktree.d.ts +17 -4
- package/dist/commands/worktree.d.ts.map +1 -1
- package/dist/commands/worktree.js +28 -4
- package/dist/commands/worktree.js.map +1 -1
- package/dist/core/AgentWorktreeDetector.d.ts +63 -0
- package/dist/core/AgentWorktreeDetector.d.ts.map +1 -0
- package/dist/core/AgentWorktreeDetector.js +329 -0
- package/dist/core/AgentWorktreeDetector.js.map +1 -0
- package/dist/core/InstarWorktreeManager.d.ts +99 -0
- package/dist/core/InstarWorktreeManager.d.ts.map +1 -0
- package/dist/core/InstarWorktreeManager.js +447 -0
- package/dist/core/InstarWorktreeManager.js.map +1 -0
- package/dist/core/MachineIdentity.d.ts.map +1 -1
- package/dist/core/MachineIdentity.js +2 -0
- package/dist/core/MachineIdentity.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +25 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +258 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/memory/NativeModuleHealer.d.ts +15 -0
- package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
- package/dist/memory/NativeModuleHealer.js +22 -0
- package/dist/memory/NativeModuleHealer.js.map +1 -1
- package/dist/monitoring/NativeHealDegradationBridge.d.ts +32 -0
- package/dist/monitoring/NativeHealDegradationBridge.d.ts.map +1 -0
- package/dist/monitoring/NativeHealDegradationBridge.js +61 -0
- package/dist/monitoring/NativeHealDegradationBridge.js.map +1 -0
- package/dist/monitoring/PromptGate.d.ts +12 -0
- package/dist/monitoring/PromptGate.d.ts.map +1 -1
- package/dist/monitoring/PromptGate.js +44 -0
- package/dist/monitoring/PromptGate.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +17 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +6 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lint-no-direct-destructive.js +7 -0
- package/scripts/resolve-release-tier.mjs +192 -0
- package/src/data/builtin-manifest.json +57 -49
- package/src/scaffold/templates.ts +17 -0
- package/src/templates/scripts/instar-watchdog.sh +169 -6
- package/src/templates/scripts/instar-worktree-create.sh +79 -0
- package/upgrades/1.1.0.md +320 -0
- package/upgrades/1.1.1.md +58 -0
- package/upgrades/side-effects/agent-worktree-convention-layer-1-2-5.md +280 -0
- package/upgrades/side-effects/agent-worktree-convention-layer-3-4.md +285 -0
- package/upgrades/side-effects/feat-fix-parent-framework-intercept.md +59 -0
- package/upgrades/side-effects/feat-install-claude-gating.md +67 -0
- package/upgrades/side-effects/feat-install-framework-flag.md +65 -0
- package/upgrades/side-effects/feat-portability-shadow-capabilities.md +63 -0
- package/upgrades/side-effects/feat-setup-framework-flag.md +65 -0
- package/upgrades/side-effects/lockdown-layer-2.md +123 -0
- package/upgrades/side-effects/native-heal-degradation-bridge.md +206 -0
- package/upgrades/side-effects/watchdog-bind-failure-probe.md +174 -0
|
@@ -24,6 +24,7 @@ import os from 'node:os';
|
|
|
24
24
|
import { execFileSync } from 'node:child_process';
|
|
25
25
|
import crypto from 'node:crypto';
|
|
26
26
|
import { SafeGitExecutor } from './SafeGitExecutor.js';
|
|
27
|
+
import { resolveAgentHome as resolveAgentHomeForWorktree } from './InstarWorktreeManager.js';
|
|
27
28
|
import { fileURLToPath } from 'node:url';
|
|
28
29
|
import { TreeGenerator } from '../knowledge/TreeGenerator.js';
|
|
29
30
|
import { HTTP_HOOK_TEMPLATES, buildHttpHookSettings } from '../data/http-hook-templates.js';
|
|
@@ -126,6 +127,7 @@ export class PostUpdateMigrator {
|
|
|
126
127
|
};
|
|
127
128
|
this.migrateHooks(result);
|
|
128
129
|
this.migrateClaudeMd(result);
|
|
130
|
+
this.migrateFrameworkShadowCapabilities(result);
|
|
129
131
|
this.migrateScripts(result);
|
|
130
132
|
this.migrateSettings(result);
|
|
131
133
|
this.migrateConfig(result);
|
|
@@ -144,6 +146,7 @@ export class PostUpdateMigrator {
|
|
|
144
146
|
this.migrateFleetWatchdog(result);
|
|
145
147
|
this.migrateParitySentinelTrust(result);
|
|
146
148
|
this.migrateConversationalCatalogPlaybookManifest(result);
|
|
149
|
+
this.migrateWorktreeConvention(result);
|
|
147
150
|
return result;
|
|
148
151
|
}
|
|
149
152
|
// ── Conversational-action catalog Playbook manifest (v0.2) ─────────
|
|
@@ -206,6 +209,138 @@ export class PostUpdateMigrator {
|
|
|
206
209
|
fs.writeFileSync(targetPath, templateContent);
|
|
207
210
|
result.upgraded.push('conversational-catalog-playbook: installed/updated manifest template');
|
|
208
211
|
}
|
|
212
|
+
// ── Agent worktree convention (Layer 3) ────────────────────────────────
|
|
213
|
+
//
|
|
214
|
+
// Spec: docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md §"Layer 3 —
|
|
215
|
+
// PostUpdateMigrator step (single-agent scope)".
|
|
216
|
+
//
|
|
217
|
+
// For every agent whose binary just updated, ensure the on-disk surface
|
|
218
|
+
// is wired up so the convention works:
|
|
219
|
+
// 1. Install/refresh `<agent_home>/.bin/instar-worktree-create.sh`
|
|
220
|
+
// (always-overwrite, per Migration Parity Standard).
|
|
221
|
+
// 2. `<agent_home>/.gitignore` already covered by `migrateGitignore`
|
|
222
|
+
// consumers; we add a one-line direct ensure here as
|
|
223
|
+
// defense-in-depth for hosts whose project gitignore isn't reached.
|
|
224
|
+
// 3. Ensure `<agent_home>/.worktrees/` exists with `0700`.
|
|
225
|
+
// 4. If a pre-existing `worktree.repoUrlAllowlist` config blocks the
|
|
226
|
+
// resolved instar repo (or no repo is reachable), emit one
|
|
227
|
+
// AttentionItem so the operator can fix before the wrapper runs.
|
|
228
|
+
//
|
|
229
|
+
// Refuses any filesystem mutation when:
|
|
230
|
+
// - The agent home doesn't pass `<instarHome>/agents/<name>/` shape +
|
|
231
|
+
// registry-membership validation (project-bound agents living
|
|
232
|
+
// somewhere else simply opt out — the wrapper isn't applicable).
|
|
233
|
+
// - `<agent_home>/.bin` exists as a symlink (defeats the
|
|
234
|
+
// /usr/local/bin clobber attack surface).
|
|
235
|
+
migrateWorktreeConvention(result) {
|
|
236
|
+
const agentHome = path.dirname(this.config.stateDir);
|
|
237
|
+
// Validate the agent home against Layer 1's contract. We import the
|
|
238
|
+
// resolver from InstarWorktreeManager so the rule lives in exactly one
|
|
239
|
+
// place. If validation fails, the convention doesn't apply to this
|
|
240
|
+
// agent and we skip silently (no error — project-bound agents with
|
|
241
|
+
// bespoke layouts pass through unchanged).
|
|
242
|
+
let resolved;
|
|
243
|
+
try {
|
|
244
|
+
resolved = resolveAgentHomeForWorktree({ env: { INSTAR_AGENT_HOME: agentHome } });
|
|
245
|
+
}
|
|
246
|
+
catch (err) {
|
|
247
|
+
result.skipped.push(`worktree-convention: agent home does not match the convention (${err instanceof Error ? err.message.split('\n')[0] : String(err)})`);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
// `.bin/` must be a real directory inside the agent home.
|
|
251
|
+
const binDir = path.join(resolved.agentHome, '.bin');
|
|
252
|
+
if (fs.existsSync(binDir)) {
|
|
253
|
+
try {
|
|
254
|
+
const lst = fs.lstatSync(binDir);
|
|
255
|
+
if (lst.isSymbolicLink()) {
|
|
256
|
+
result.errors.push(`worktree-convention: ${binDir} is a symlink — refused (defeats /usr/local/bin clobber surface)`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
result.errors.push(`worktree-convention: stat ${binDir} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
fs.mkdirSync(binDir, { recursive: true, mode: 0o755 });
|
|
267
|
+
}
|
|
268
|
+
// Always-overwrite the wrapper (Migration Parity Standard for hook
|
|
269
|
+
// scripts: built-in templates are authoritative).
|
|
270
|
+
const wrapperTargetPath = path.join(binDir, 'instar-worktree-create.sh');
|
|
271
|
+
const templateCandidates = [
|
|
272
|
+
path.resolve(__dirname, '..', 'templates', 'scripts', 'instar-worktree-create.sh'),
|
|
273
|
+
path.resolve(__dirname, '..', '..', 'src', 'templates', 'scripts', 'instar-worktree-create.sh'),
|
|
274
|
+
];
|
|
275
|
+
let templatePath = '';
|
|
276
|
+
for (const cand of templateCandidates) {
|
|
277
|
+
if (fs.existsSync(cand)) {
|
|
278
|
+
templatePath = cand;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (!templatePath) {
|
|
283
|
+
result.skipped.push('worktree-convention: wrapper template not found in package install');
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
288
|
+
const existing = fs.existsSync(wrapperTargetPath)
|
|
289
|
+
? fs.readFileSync(wrapperTargetPath, 'utf-8')
|
|
290
|
+
: null;
|
|
291
|
+
if (existing !== templateContent) {
|
|
292
|
+
fs.writeFileSync(wrapperTargetPath, templateContent, { mode: 0o755 });
|
|
293
|
+
// Re-assert mode (umask masking can drop the +x bit on existing files).
|
|
294
|
+
fs.chmodSync(wrapperTargetPath, 0o755);
|
|
295
|
+
result.upgraded.push('worktree-convention: installed/refreshed instar-worktree-create.sh');
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// Re-assert mode even when content matches — defends against an
|
|
299
|
+
// operator's `chmod -R` that may have dropped the +x bit.
|
|
300
|
+
try {
|
|
301
|
+
fs.chmodSync(wrapperTargetPath, 0o755);
|
|
302
|
+
}
|
|
303
|
+
catch { /* @silent-fallback-ok — best-effort */ }
|
|
304
|
+
result.skipped.push('worktree-convention: instar-worktree-create.sh up to date');
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
result.errors.push(`worktree-convention: wrapper install failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// Ensure `<agent_home>/.gitignore` contains `.worktrees/`. Defense-in-
|
|
312
|
+
// depth — the v1.1.0 `GITIGNORE_ENTRIES` change covers fresh inits and
|
|
313
|
+
// any agent whose .gitignore flows through `ensureGitignore` on
|
|
314
|
+
// update, but agents with hand-crafted gitignores may not reach it.
|
|
315
|
+
try {
|
|
316
|
+
const gitignorePath = path.join(resolved.agentHome, '.gitignore');
|
|
317
|
+
const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
318
|
+
const hasEntry = /^\s*\.worktrees\/?\s*$/m.test(existing);
|
|
319
|
+
if (!hasEntry) {
|
|
320
|
+
const sep = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
|
|
321
|
+
const block = `${sep}\n# Sandbox-safe worktrees (per-machine; multi-GB foreign-repo contents)\n.worktrees/\n`;
|
|
322
|
+
fs.writeFileSync(gitignorePath, existing + block);
|
|
323
|
+
result.upgraded.push('worktree-convention: added .worktrees/ to agent-home .gitignore');
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
result.skipped.push('worktree-convention: .gitignore already excludes .worktrees/');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
result.errors.push(`worktree-convention: gitignore patch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
331
|
+
}
|
|
332
|
+
// Ensure `<agent_home>/.worktrees/` exists with 0700.
|
|
333
|
+
try {
|
|
334
|
+
const worktreesDir = path.join(resolved.agentHome, '.worktrees');
|
|
335
|
+
if (!fs.existsSync(worktreesDir)) {
|
|
336
|
+
fs.mkdirSync(worktreesDir, { recursive: true, mode: 0o700 });
|
|
337
|
+
}
|
|
338
|
+
fs.chmodSync(worktreesDir, 0o700);
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
result.errors.push(`worktree-convention: .worktrees/ ensure failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
209
344
|
/**
|
|
210
345
|
* Async migration superset — runs the sync migrate() then performs the
|
|
211
346
|
* async parity-renderings backfill that needs rule.remediate() Promises.
|
|
@@ -2012,6 +2147,26 @@ The user has been talking to you (possibly for days). A generic greeting like "H
|
|
|
2012
2147
|
else {
|
|
2013
2148
|
result.skipped.push('CLAUDE.md: File Viewer section already present');
|
|
2014
2149
|
}
|
|
2150
|
+
// Worktree Convention section (Migration Parity Standard backfill for
|
|
2151
|
+
// Layer 2 of the agent worktree convention — fresh inits get this via
|
|
2152
|
+
// generateClaudeMd; existing agents get it here on update).
|
|
2153
|
+
if (!content.includes('Worktree Convention') && !content.includes('instar worktree create <branch>')) {
|
|
2154
|
+
const section = `
|
|
2155
|
+
## Worktree Convention
|
|
2156
|
+
|
|
2157
|
+
Create worktrees for collaborator repos with \`instar worktree create <branch>\` — it resolves your agent's home automatically. Never hardcode another agent's name or place worktrees inside the shared checkout.
|
|
2158
|
+
|
|
2159
|
+
**Why:** the macOS sandbox can revoke filesystem access to anything outside the agent home mid-session, with no in-session recovery path. The agent home (\`~/.instar/agents/<agent>/\`) is the one location the sandbox cannot revoke. \`instar worktree create\` places the worktree at \`~/.instar/agents/<agent>/.worktrees/<slug>/\` and refuses any other destination. Spec: \`docs/specs/AGENT-WORKTREE-CONVENTION-SPEC.md\`.
|
|
2160
|
+
|
|
2161
|
+
**Caveat — git identity env vars:** the CLI sets per-worktree \`user.name\` / \`user.email\` to \`Instar Agent (<name>)\` / \`<name>@instar.local\`. \`GIT_AUTHOR_NAME\` / \`GIT_COMMITTER_EMAIL\` in the calling environment override that local config. Agents that care about commit attribution must avoid exporting those vars.
|
|
2162
|
+
`;
|
|
2163
|
+
content += '\n' + section;
|
|
2164
|
+
patched = true;
|
|
2165
|
+
result.upgraded.push('CLAUDE.md: added Worktree Convention section');
|
|
2166
|
+
}
|
|
2167
|
+
else {
|
|
2168
|
+
result.skipped.push('CLAUDE.md: Worktree Convention section already present');
|
|
2169
|
+
}
|
|
2015
2170
|
if (patched) {
|
|
2016
2171
|
try {
|
|
2017
2172
|
fs.writeFileSync(claudeMdPath, content);
|
|
@@ -2021,6 +2176,109 @@ The user has been talking to you (possibly for days). A generic greeting like "H
|
|
|
2021
2176
|
}
|
|
2022
2177
|
}
|
|
2023
2178
|
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Mirror capability-instruction sections from the freshly-patched CLAUDE.md
|
|
2181
|
+
* into any non-Claude framework shadows (AGENTS.md, GEMINI.md) that exist
|
|
2182
|
+
* on disk. Portability audit Gap 6 minimal-shim implementation:
|
|
2183
|
+
* `generateClaudeMd` + `migrateClaudeMd` produce a rich capability/
|
|
2184
|
+
* instructions document for Claude Code, but Codex/Gemini shadows had no
|
|
2185
|
+
* equivalent — agents on those runtimes received the canonical identity
|
|
2186
|
+
* (Gap 1, shipped) but none of the "here's what you can do" sections.
|
|
2187
|
+
*
|
|
2188
|
+
* The shim slices each known capability section out of CLAUDE.md (from its
|
|
2189
|
+
* marker through the start of the next top-level heading) and appends any
|
|
2190
|
+
* section missing from the shadow. The section bodies are NOT duplicated
|
|
2191
|
+
* — they are literally copied from the just-patched CLAUDE.md, so the two
|
|
2192
|
+
* cannot drift. Runs AFTER `migrateClaudeMd` so the source is current.
|
|
2193
|
+
*
|
|
2194
|
+
* Idempotent: each section is only appended when its marker is absent from
|
|
2195
|
+
* the shadow. No-op when CLAUDE.md is absent or no shadow exists. Safe for
|
|
2196
|
+
* Claude-only installs (they have no AGENTS.md/GEMINI.md so nothing
|
|
2197
|
+
* happens). A full refactor that extracts the section *bodies* into a
|
|
2198
|
+
* shared array is deliberately NOT done here per the operator's
|
|
2199
|
+
* "minimal shim" decision — that would touch ~360 lines of inline content
|
|
2200
|
+
* in `migrateClaudeMd` and is high-risk for low marginal benefit.
|
|
2201
|
+
*/
|
|
2202
|
+
migrateFrameworkShadowCapabilities(result) {
|
|
2203
|
+
const claudeMdPath = path.join(this.config.projectDir, 'CLAUDE.md');
|
|
2204
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
2205
|
+
result.skipped.push('shadow capabilities (CLAUDE.md absent — nothing to mirror)');
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
let claudeMd;
|
|
2209
|
+
try {
|
|
2210
|
+
claudeMd = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
2211
|
+
}
|
|
2212
|
+
catch (err) {
|
|
2213
|
+
result.errors.push(`shadow capabilities: CLAUDE.md read failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
// Markers identifying each capability section migrateClaudeMd may have
|
|
2217
|
+
// ensured exists in CLAUDE.md. Kept in document order so appended
|
|
2218
|
+
// sections preserve narrative ordering in the shadow.
|
|
2219
|
+
const markers = [
|
|
2220
|
+
'### Self-Discovery',
|
|
2221
|
+
'**Private Viewing**',
|
|
2222
|
+
'**Cloudflare Tunnel**',
|
|
2223
|
+
'**Dashboard**',
|
|
2224
|
+
'**File Viewer',
|
|
2225
|
+
'### Coherence Gate',
|
|
2226
|
+
'### External Operation Safety',
|
|
2227
|
+
'### Playbook — Adaptive Context Engineering',
|
|
2228
|
+
'## Threadline Network (Agent-to-Agent Communication)',
|
|
2229
|
+
'## Worktree Convention',
|
|
2230
|
+
];
|
|
2231
|
+
for (const shadowName of ['AGENTS.md', 'GEMINI.md']) {
|
|
2232
|
+
const shadowPath = path.join(this.config.projectDir, shadowName);
|
|
2233
|
+
if (!fs.existsSync(shadowPath))
|
|
2234
|
+
continue;
|
|
2235
|
+
let shadowContent;
|
|
2236
|
+
try {
|
|
2237
|
+
shadowContent = fs.readFileSync(shadowPath, 'utf-8');
|
|
2238
|
+
}
|
|
2239
|
+
catch (err) {
|
|
2240
|
+
result.errors.push(`${shadowName} read: ${err instanceof Error ? err.message : String(err)}`);
|
|
2241
|
+
continue;
|
|
2242
|
+
}
|
|
2243
|
+
let appended = shadowContent;
|
|
2244
|
+
let mirrored = 0;
|
|
2245
|
+
for (const marker of markers) {
|
|
2246
|
+
if (appended.includes(marker))
|
|
2247
|
+
continue;
|
|
2248
|
+
const start = claudeMd.indexOf(marker);
|
|
2249
|
+
if (start < 0)
|
|
2250
|
+
continue;
|
|
2251
|
+
// Slice from the marker through the start of the next top-level
|
|
2252
|
+
// heading (##/### line) or EOF. The leading "\n" guard avoids
|
|
2253
|
+
// matching headings inside fenced code blocks at column 0 only
|
|
2254
|
+
// when they begin a line.
|
|
2255
|
+
const after = claudeMd.slice(start);
|
|
2256
|
+
// Skip past the marker's own header line, then look for the next
|
|
2257
|
+
// heading. Without this, a marker that itself starts with "###"
|
|
2258
|
+
// would zero-length match.
|
|
2259
|
+
const nlAfterMarker = after.indexOf('\n');
|
|
2260
|
+
const searchFrom = nlAfterMarker >= 0 ? nlAfterMarker + 1 : 0;
|
|
2261
|
+
const tail = after.slice(searchFrom);
|
|
2262
|
+
const nextRel = tail.search(/(^|\n)(##|###) [^#\n]/);
|
|
2263
|
+
const sectionEnd = nextRel >= 0 ? searchFrom + nextRel : after.length;
|
|
2264
|
+
const section = after.slice(0, sectionEnd).trimEnd();
|
|
2265
|
+
appended = appended.trimEnd() + '\n\n' + section + '\n';
|
|
2266
|
+
mirrored++;
|
|
2267
|
+
}
|
|
2268
|
+
if (mirrored > 0) {
|
|
2269
|
+
try {
|
|
2270
|
+
fs.writeFileSync(shadowPath, appended);
|
|
2271
|
+
result.upgraded.push(`${shadowName}: mirrored ${mirrored} capability section(s) from CLAUDE.md`);
|
|
2272
|
+
}
|
|
2273
|
+
catch (err) {
|
|
2274
|
+
result.errors.push(`${shadowName} write: ${err instanceof Error ? err.message : String(err)}`);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
else {
|
|
2278
|
+
result.skipped.push(`${shadowName}: capability sections already present`);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2024
2282
|
/**
|
|
2025
2283
|
* Install any new scripts that don't exist yet.
|
|
2026
2284
|
* Never overwrites existing scripts (user may have customized them).
|