@elmundi/ship-cli 0.14.2 → 0.15.4
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 +17 -16
- package/bin/shipctl.mjs +4 -80
- package/lib/commands/feedback.mjs +1 -1
- package/lib/commands/help.mjs +47 -131
- package/lib/commands/init.mjs +17 -250
- package/lib/commands/knowledge.mjs +25 -328
- package/lib/commands/preflight.mjs +213 -0
- package/lib/commands/run.mjs +298 -119
- package/lib/commands/trigger.mjs +95 -10
- package/lib/config/schema.mjs +73 -11
- package/lib/http.mjs +0 -2
- package/lib/runtime/routines.mjs +39 -0
- package/lib/templates.mjs +2 -2
- package/lib/verify/checks/agents-on-disk.mjs +5 -28
- package/lib/verify/registry.mjs +7 -8
- package/package.json +1 -1
- package/lib/artifacts/fs-index.mjs +0 -230
- package/lib/cache/store.mjs +0 -422
- package/lib/commands/bootstrap.mjs +0 -4
- package/lib/commands/callback.mjs +0 -742
- package/lib/commands/docs.mjs +0 -90
- package/lib/commands/kickoff.mjs +0 -192
- package/lib/commands/lanes.mjs +0 -566
- package/lib/commands/manifest-catalog.mjs +0 -251
- package/lib/commands/migrate.mjs +0 -204
- package/lib/commands/new.mjs +0 -452
- package/lib/commands/patterns.mjs +0 -160
- package/lib/commands/process.mjs +0 -388
- package/lib/commands/search.mjs +0 -43
- package/lib/commands/sync.mjs +0 -824
- package/lib/config/migrate.mjs +0 -223
- package/lib/find-ship-root.mjs +0 -75
- package/lib/process/specialist-prompt-contract.mjs +0 -171
- package/lib/state/lockfile.mjs +0 -180
- package/lib/vendor/run-agent.workflow.yml +0 -254
- package/lib/verify/checks/artifacts-up-to-date.mjs +0 -78
- package/lib/verify/checks/cache-integrity.mjs +0 -51
- package/lib/verify/checks/gitignore-cache.mjs +0 -51
- package/lib/verify/checks/rules-markers.mjs +0 -135
package/lib/commands/init.mjs
CHANGED
|
@@ -25,17 +25,10 @@ import {
|
|
|
25
25
|
CONFIG_REL,
|
|
26
26
|
STATE_REL,
|
|
27
27
|
} from "../config/io.mjs";
|
|
28
|
-
import { syncArtifacts } from "./sync.mjs";
|
|
29
28
|
import { detectAll } from "../adapters/index.mjs";
|
|
30
|
-
import { listCached, readCached } from "../cache/store.mjs";
|
|
31
29
|
import { renderPlan, applyPlan } from "../bootstrap/render.mjs";
|
|
32
30
|
import { KNOWN_AGENTS } from "../detect.mjs";
|
|
33
31
|
|
|
34
|
-
const MARKER = "<!-- ship-cli: artifacts-protocol v1 -->";
|
|
35
|
-
const END_MARKER = "<!-- ship-cli:end artifacts-protocol -->";
|
|
36
|
-
const INSTALLED_FROM_RE = /<!--\s*ship-cli:\s*installed-from\s+([^\s>]+)\s*-->/g;
|
|
37
|
-
const FOOTER_VERSION_RE = /@(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)\s*$/;
|
|
38
|
-
|
|
39
32
|
/**
|
|
40
33
|
* @typedef {{
|
|
41
34
|
* cwd:string,
|
|
@@ -160,12 +153,9 @@ export async function initCommand(ctx, args) {
|
|
|
160
153
|
}
|
|
161
154
|
for (const w of valid.warnings) process.stderr.write(`warn: ${w}\n`);
|
|
162
155
|
|
|
163
|
-
// ── Derived artifact list to fetch via syncArtifacts ─────────────────────
|
|
164
|
-
const derived = buildDerivedList(config, opts);
|
|
165
|
-
|
|
166
156
|
// ── Dry-run short-circuit: emit plan only, write nothing ─────────────────
|
|
167
157
|
if (opts.dryRun) {
|
|
168
|
-
const plan = buildPlanSummary(opts.cwd, config, opts, telemetryMode,
|
|
158
|
+
const plan = buildPlanSummary(opts.cwd, config, opts, telemetryMode, []);
|
|
169
159
|
if (opts.json) {
|
|
170
160
|
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
171
161
|
} else {
|
|
@@ -182,48 +172,27 @@ export async function initCommand(ctx, args) {
|
|
|
182
172
|
throw new Error("init: failed to locate .ship/ after creation");
|
|
183
173
|
}
|
|
184
174
|
|
|
185
|
-
//
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
cwd: shipRoot,
|
|
191
|
-
baseUrl: ctx.baseUrl,
|
|
192
|
-
channel: opts.channel || config.api?.channel,
|
|
193
|
-
onlyKinds: ["collection"],
|
|
194
|
-
include: derived,
|
|
195
|
-
verbose: false,
|
|
196
|
-
});
|
|
197
|
-
} catch (e) {
|
|
198
|
-
const msg = e && e.message ? e.message : String(e);
|
|
199
|
-
process.stderr.write(`warn: artifact fetch partially failed (${msg})\n`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ── --copy-rules: materialize agent rules from cache onto disk ───────────
|
|
175
|
+
// Phase 2.5 retired the local artifact-fetch / agent-rule install
|
|
176
|
+
// path. Agent rule files (CLAUDE.md, AGENTS.md, .cursor/rules/...)
|
|
177
|
+
// are baked into the wizard's seed PR instead — no syncArtifacts
|
|
178
|
+
// call here, no installAgentRule loop, no playbook copy.
|
|
179
|
+
const syncSummary = null;
|
|
204
180
|
const ruleInstallations = [];
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
181
|
+
const playbookCopied = null;
|
|
182
|
+
if (opts.copyRules || opts.copyPlaybook) {
|
|
183
|
+
process.stderr.write(
|
|
184
|
+
"warn: --copy-rules / --copy-playbook are no-ops after Phase 2.5 — agent rule files now ship in the wizard's seed PR.\n",
|
|
185
|
+
);
|
|
210
186
|
}
|
|
211
187
|
|
|
212
188
|
// ── --bootstrap: render CI/tracker scaffolding ───────────────────────────
|
|
213
189
|
let bootstrapSummary = null;
|
|
214
190
|
if (opts.bootstrap) {
|
|
215
|
-
const
|
|
216
|
-
const plan = renderPlan(config, presetArtifact);
|
|
191
|
+
const plan = renderPlan(config, null);
|
|
217
192
|
const results = applyPlan(shipRoot, plan, { dryRun: false, force: opts.force });
|
|
218
193
|
bootstrapSummary = { files: plan.summary.files, notes: plan.summary.notes, results };
|
|
219
194
|
}
|
|
220
195
|
|
|
221
|
-
// ── --copy-playbook: copy cached playbook to .ship/playbooks/ ────────────
|
|
222
|
-
let playbookCopied = null;
|
|
223
|
-
if (opts.copyPlaybook) {
|
|
224
|
-
playbookCopied = copyPlaybookFromCache(shipRoot);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
196
|
// ── Output ───────────────────────────────────────────────────────────────
|
|
228
197
|
const summary = {
|
|
229
198
|
ok: true,
|
|
@@ -433,32 +402,6 @@ function proposeStack(findings) {
|
|
|
433
402
|
}
|
|
434
403
|
|
|
435
404
|
// ── Derived artifact list ──────────────────────────────────────────────────
|
|
436
|
-
/**
|
|
437
|
-
* @param {object} config
|
|
438
|
-
* @param {InitOptions} opts
|
|
439
|
-
* @returns {Array<{kind:string,id:string}>}
|
|
440
|
-
*/
|
|
441
|
-
function buildDerivedList(config, opts) {
|
|
442
|
-
const list = [];
|
|
443
|
-
const seen = new Set();
|
|
444
|
-
const add = (kind, id) => {
|
|
445
|
-
const key = `${kind}:${id}`;
|
|
446
|
-
if (seen.has(key)) return;
|
|
447
|
-
seen.add(key);
|
|
448
|
-
list.push({ kind, id });
|
|
449
|
-
};
|
|
450
|
-
for (const a of config.stack.agents || []) {
|
|
451
|
-
add("collection", `agent-rules-${a}`);
|
|
452
|
-
}
|
|
453
|
-
if (config.stack.preset) {
|
|
454
|
-
add("collection", `preset-${config.stack.preset}`);
|
|
455
|
-
}
|
|
456
|
-
if (opts.copyPlaybook) {
|
|
457
|
-
add("collection", "adoption-playbook");
|
|
458
|
-
}
|
|
459
|
-
return list;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
405
|
// ── Ship layout creation ───────────────────────────────────────────────────
|
|
463
406
|
function ensureShipLayout(cwd, config, configFilePath, configExisted) {
|
|
464
407
|
const shipDir = path.join(cwd, SHIP_DIR);
|
|
@@ -523,178 +466,6 @@ function ensureGitignore(cwd) {
|
|
|
523
466
|
* @param {{force:boolean}} opts
|
|
524
467
|
* @returns {null | {agent:string, path:string, action:string, from:string}}
|
|
525
468
|
*/
|
|
526
|
-
function installAgentRule(shipRoot, agent, { force }) {
|
|
527
|
-
const id = `agent-rules-${agent}`;
|
|
528
|
-
const version = latestCachedVersion(shipRoot, "collection", id);
|
|
529
|
-
if (!version) {
|
|
530
|
-
process.stderr.write(
|
|
531
|
-
`warn: --copy-rules: no cached artifact for collection/${id} (was the fetch successful?)\n`,
|
|
532
|
-
);
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
const cached = readCached(shipRoot, "collection", id, version);
|
|
536
|
-
if (!cached) return null;
|
|
537
|
-
const parsed = parseFrontmatter(cached.content);
|
|
538
|
-
const target = parsed.attrs.install_target || fallbackInstallTarget(agent);
|
|
539
|
-
if (!target) {
|
|
540
|
-
process.stderr.write(`warn: --copy-rules: no install_target for ${agent}; skipping\n`);
|
|
541
|
-
return null;
|
|
542
|
-
}
|
|
543
|
-
const marker = parsed.attrs.marker || MARKER;
|
|
544
|
-
const body = parsed.body.trimEnd();
|
|
545
|
-
const footer = `<!-- ship-cli: installed-from collection/${id}@${version} -->`;
|
|
546
|
-
|
|
547
|
-
const absTarget = path.isAbsolute(target) ? target : path.join(shipRoot, target);
|
|
548
|
-
const existed = fs.existsSync(absTarget);
|
|
549
|
-
const prev = existed ? fs.readFileSync(absTarget, "utf8") : "";
|
|
550
|
-
|
|
551
|
-
// If installed-from references a different version and --force is not set, skip.
|
|
552
|
-
if (existed) {
|
|
553
|
-
const existingVersion = extractInstalledVersion(prev);
|
|
554
|
-
if (existingVersion && existingVersion !== version && !force) {
|
|
555
|
-
process.stderr.write(
|
|
556
|
-
`warn: ${path.relative(shipRoot, absTarget)} has ship-cli installed-from @${existingVersion}; pass --force to replace with @${version}\n`,
|
|
557
|
-
);
|
|
558
|
-
return {
|
|
559
|
-
agent,
|
|
560
|
-
path: path.relative(shipRoot, absTarget) || absTarget,
|
|
561
|
-
action: "skipped",
|
|
562
|
-
from: `collection/${id}@${version}`,
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const next = upsertMarkedBlock(prev, { marker, endMarker: END_MARKER, body, footer });
|
|
568
|
-
|
|
569
|
-
fs.mkdirSync(path.dirname(absTarget), { recursive: true });
|
|
570
|
-
fs.writeFileSync(absTarget, next, "utf8");
|
|
571
|
-
|
|
572
|
-
return {
|
|
573
|
-
agent,
|
|
574
|
-
path: path.relative(shipRoot, absTarget) || absTarget,
|
|
575
|
-
action: existed ? "updated" : "wrote",
|
|
576
|
-
from: `collection/${id}@${version}`,
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
function fallbackInstallTarget(agent) {
|
|
581
|
-
const meta = KNOWN_AGENTS[agent];
|
|
582
|
-
if (!meta) return null;
|
|
583
|
-
return path.join(...meta.targetRel);
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* Parse a YAML front-matter block if present.
|
|
588
|
-
* @param {string} text
|
|
589
|
-
* @returns {{attrs:object, body:string}}
|
|
590
|
-
*/
|
|
591
|
-
function parseFrontmatter(text) {
|
|
592
|
-
if (!text.startsWith("---\n") && !text.startsWith("---\r\n")) {
|
|
593
|
-
return { attrs: {}, body: text };
|
|
594
|
-
}
|
|
595
|
-
const rest = text.slice(4);
|
|
596
|
-
const endIdx = rest.indexOf("\n---\n");
|
|
597
|
-
const altEndIdx = rest.indexOf("\n---\r\n");
|
|
598
|
-
const end = endIdx >= 0 ? endIdx : altEndIdx >= 0 ? altEndIdx : -1;
|
|
599
|
-
if (end < 0) return { attrs: {}, body: text };
|
|
600
|
-
const fmText = rest.slice(0, end);
|
|
601
|
-
const body = rest.slice(end + (endIdx >= 0 ? 5 : 6));
|
|
602
|
-
let attrs = {};
|
|
603
|
-
try {
|
|
604
|
-
const parsed = YAML.parse(fmText);
|
|
605
|
-
if (parsed && typeof parsed === "object") attrs = parsed;
|
|
606
|
-
} catch {
|
|
607
|
-
attrs = {};
|
|
608
|
-
}
|
|
609
|
-
return { attrs, body };
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* Idempotent upsert of a marker-delimited block + footer line.
|
|
614
|
-
* prev : current file text (may be empty)
|
|
615
|
-
* marker, endMarker : <!-- … --> tokens
|
|
616
|
-
* body : replacement body (should contain or wrap with the markers itself;
|
|
617
|
-
* we just splice it in verbatim)
|
|
618
|
-
* footer : single line `<!-- ship-cli: installed-from … -->`
|
|
619
|
-
*/
|
|
620
|
-
function upsertMarkedBlock(prev, { marker, endMarker, body, footer }) {
|
|
621
|
-
// Strip every existing "installed-from" footer so we never duplicate them.
|
|
622
|
-
let stripped = prev.replace(INSTALLED_FROM_RE, "");
|
|
623
|
-
// Collapse any 3+ consecutive newlines left by the strip.
|
|
624
|
-
stripped = stripped.replace(/\n{3,}/g, "\n\n");
|
|
625
|
-
|
|
626
|
-
let out;
|
|
627
|
-
if (stripped.includes(marker) && stripped.includes(endMarker)) {
|
|
628
|
-
const start = stripped.indexOf(marker);
|
|
629
|
-
const endAt = stripped.indexOf(endMarker, start) + endMarker.length;
|
|
630
|
-
const before = stripped.slice(0, start).replace(/\s+$/, "");
|
|
631
|
-
const after = stripped.slice(endAt).replace(/^\s+/, "");
|
|
632
|
-
out = `${before}${before ? "\n\n" : ""}${body.trim()}\n${after ? `\n${after}` : ""}`;
|
|
633
|
-
} else if (stripped.trim().length === 0) {
|
|
634
|
-
out = `${body.trim()}\n`;
|
|
635
|
-
} else {
|
|
636
|
-
out = `${stripped.replace(/\s+$/, "")}\n\n${body.trim()}\n`;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Append a single footer line.
|
|
640
|
-
out = `${out.replace(/\s+$/, "")}\n\n${footer}\n`;
|
|
641
|
-
return out;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function extractInstalledVersion(text) {
|
|
645
|
-
const matches = [...text.matchAll(INSTALLED_FROM_RE)];
|
|
646
|
-
if (!matches.length) return null;
|
|
647
|
-
const last = matches[matches.length - 1][1];
|
|
648
|
-
const m = last.match(FOOTER_VERSION_RE);
|
|
649
|
-
return m ? m[1] : null;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// ── Cache helpers ──────────────────────────────────────────────────────────
|
|
653
|
-
function latestCachedVersion(shipRoot, kind, id) {
|
|
654
|
-
const all = listCached(shipRoot).filter((c) => c.kind === kind && c.id === id);
|
|
655
|
-
if (!all.length) return null;
|
|
656
|
-
all.sort((a, b) => cmpSemver(b.version, a.version));
|
|
657
|
-
return all[0].version;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function cmpSemver(a, b) {
|
|
661
|
-
const pa = String(a).split(/[.-]/).map((x) => (Number.isNaN(Number(x)) ? x : Number(x)));
|
|
662
|
-
const pb = String(b).split(/[.-]/).map((x) => (Number.isNaN(Number(x)) ? x : Number(x)));
|
|
663
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
664
|
-
const xa = pa[i];
|
|
665
|
-
const xb = pb[i];
|
|
666
|
-
if (xa === undefined) return -1;
|
|
667
|
-
if (xb === undefined) return 1;
|
|
668
|
-
if (xa === xb) continue;
|
|
669
|
-
if (typeof xa === typeof xb) return xa < xb ? -1 : 1;
|
|
670
|
-
return typeof xa === "number" ? -1 : 1;
|
|
671
|
-
}
|
|
672
|
-
return 0;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function readPresetArtifact(shipRoot, config) {
|
|
676
|
-
const preset = config.stack?.preset;
|
|
677
|
-
if (!preset) return null;
|
|
678
|
-
const id = `preset-${preset}`;
|
|
679
|
-
const version = latestCachedVersion(shipRoot, "collection", id);
|
|
680
|
-
if (!version) return null;
|
|
681
|
-
const cached = readCached(shipRoot, "collection", id, version);
|
|
682
|
-
if (!cached) return null;
|
|
683
|
-
const { attrs, body } = parseFrontmatter(cached.content);
|
|
684
|
-
return { id, version, attrs, body };
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
function copyPlaybookFromCache(shipRoot) {
|
|
688
|
-
const version = latestCachedVersion(shipRoot, "collection", "adoption-playbook");
|
|
689
|
-
if (!version) return null;
|
|
690
|
-
const cached = readCached(shipRoot, "collection", "adoption-playbook", version);
|
|
691
|
-
if (!cached) return null;
|
|
692
|
-
const dest = path.join(shipRoot, ".ship", "playbooks", `adoption-playbook@${version}.md`);
|
|
693
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
694
|
-
fs.writeFileSync(dest, cached.content, "utf8");
|
|
695
|
-
return { path: path.relative(shipRoot, dest), version };
|
|
696
|
-
}
|
|
697
|
-
|
|
698
469
|
// ── Telemetry prompt ───────────────────────────────────────────────────────
|
|
699
470
|
async function promptTelemetry() {
|
|
700
471
|
const rl = readline.createInterface({ input, output });
|
|
@@ -721,15 +492,11 @@ function buildPlanSummary(cwd, config, opts, telemetryMode, derived) {
|
|
|
721
492
|
language: config.stack.language,
|
|
722
493
|
agents: [...(config.stack.agents || [])],
|
|
723
494
|
};
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
`.ship/rules/${a}.md`,
|
|
730
|
-
from: `collection/agent-rules-${a}@<latest>`,
|
|
731
|
-
}))
|
|
732
|
-
: [];
|
|
495
|
+
// Phase 2.5 — agent rule files now ship in the wizard's seed PR,
|
|
496
|
+
// not in shipctl init. ``rules`` stays in the plan summary as an
|
|
497
|
+
// empty array so JSON consumers don't see the key disappear, but
|
|
498
|
+
// the human plan no longer renders a "Rules to install" section.
|
|
499
|
+
const rules = [];
|
|
733
500
|
const bootstrapPreview = opts.bootstrap
|
|
734
501
|
? renderPlan(config, null).summary
|
|
735
502
|
: null;
|