@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.
Files changed (39) hide show
  1. package/README.md +17 -16
  2. package/bin/shipctl.mjs +4 -80
  3. package/lib/commands/feedback.mjs +1 -1
  4. package/lib/commands/help.mjs +47 -131
  5. package/lib/commands/init.mjs +17 -250
  6. package/lib/commands/knowledge.mjs +25 -328
  7. package/lib/commands/preflight.mjs +213 -0
  8. package/lib/commands/run.mjs +298 -119
  9. package/lib/commands/trigger.mjs +95 -10
  10. package/lib/config/schema.mjs +73 -11
  11. package/lib/http.mjs +0 -2
  12. package/lib/runtime/routines.mjs +39 -0
  13. package/lib/templates.mjs +2 -2
  14. package/lib/verify/checks/agents-on-disk.mjs +5 -28
  15. package/lib/verify/registry.mjs +7 -8
  16. package/package.json +1 -1
  17. package/lib/artifacts/fs-index.mjs +0 -230
  18. package/lib/cache/store.mjs +0 -422
  19. package/lib/commands/bootstrap.mjs +0 -4
  20. package/lib/commands/callback.mjs +0 -742
  21. package/lib/commands/docs.mjs +0 -90
  22. package/lib/commands/kickoff.mjs +0 -192
  23. package/lib/commands/lanes.mjs +0 -566
  24. package/lib/commands/manifest-catalog.mjs +0 -251
  25. package/lib/commands/migrate.mjs +0 -204
  26. package/lib/commands/new.mjs +0 -452
  27. package/lib/commands/patterns.mjs +0 -160
  28. package/lib/commands/process.mjs +0 -388
  29. package/lib/commands/search.mjs +0 -43
  30. package/lib/commands/sync.mjs +0 -824
  31. package/lib/config/migrate.mjs +0 -223
  32. package/lib/find-ship-root.mjs +0 -75
  33. package/lib/process/specialist-prompt-contract.mjs +0 -171
  34. package/lib/state/lockfile.mjs +0 -180
  35. package/lib/vendor/run-agent.workflow.yml +0 -254
  36. package/lib/verify/checks/artifacts-up-to-date.mjs +0 -78
  37. package/lib/verify/checks/cache-integrity.mjs +0 -51
  38. package/lib/verify/checks/gitignore-cache.mjs +0 -51
  39. package/lib/verify/checks/rules-markers.mjs +0 -135
@@ -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, derived);
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
- // ── Sync relevant artifacts into .ship/cache/ ────────────────────────────
186
- let syncSummary = null;
187
- if (derived.length) {
188
- try {
189
- syncSummary = await syncArtifacts({
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
- if (opts.copyRules) {
206
- for (const agent of config.stack.agents || []) {
207
- const res = installAgentRule(shipRoot, agent, { force: opts.force });
208
- if (res) ruleInstallations.push(res);
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 presetArtifact = readPresetArtifact(shipRoot, config);
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
- const rules = opts.copyRules
725
- ? (config.stack.agents || []).map((a) => ({
726
- agent: a,
727
- path:
728
- (KNOWN_AGENTS[a] && KNOWN_AGENTS[a].targetRel.join("/")) ||
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;