@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
@@ -1,7 +1,7 @@
1
1
  import { readConfig } from "../config/io.mjs";
2
2
  import { dueLanesFromRoutines, dueRoutines } from "../runtime/routines.mjs";
3
3
 
4
- const VERSION = "v2";
4
+ const VERSION = "v3";
5
5
 
6
6
  export async function triggerCommand(ctx, rest) {
7
7
  const opts = parseArgs(rest);
@@ -12,10 +12,23 @@ export async function triggerCommand(ctx, rest) {
12
12
  const baseUrl = resolveBaseUrl(opts.baseUrl || explicitGlobalBaseUrl(ctx));
13
13
  const token = process.env.SHIP_API_TOKEN || "";
14
14
  let claimStatus = "skipped:no-token";
15
- if (token && due.length > 0 && !opts.noClaim) {
16
- let workspaceId = opts.workspace;
15
+ let workspaceId = null;
16
+ let repoId = null;
17
+ if (token && (due.length > 0 || opts.pipelineFallback) && !opts.noClaim) {
18
+ // ``SHIP_WORKSPACE_ID`` env var matches what ``shipctl run`` already
19
+ // honours (run.mjs reads it directly). Without this fallback the
20
+ // CLI burned a ``GET /v1/workspaces`` round-trip every tick and
21
+ // — if the token's user happened to have zero memberships at that
22
+ // moment — printed "No workspaces visible to this token" and
23
+ // failed the entire schedule, even though the workflow file had
24
+ // the workspace ID right there in env. Honour the env var so the
25
+ // common case skips the discovery call entirely.
26
+ workspaceId =
27
+ opts.workspace || (process.env.SHIP_WORKSPACE_ID || "").trim() || "";
17
28
  if (!workspaceId) workspaceId = await resolveSoleWorkspace(baseUrl, token);
18
- const repoId = await resolveRepoId(baseUrl, token, workspaceId, opts.repo);
29
+ repoId = await resolveRepoId(baseUrl, token, workspaceId, opts.repo);
30
+ }
31
+ if (token && due.length > 0 && !opts.noClaim) {
19
32
  const claimed = [];
20
33
  for (const routine of due) {
21
34
  const claim = await claimRoutine(baseUrl, token, workspaceId, repoId, opts.event, routine);
@@ -27,6 +40,26 @@ export async function triggerCommand(ctx, rest) {
27
40
  claimStatus = "attempted";
28
41
  }
29
42
 
43
+ // One-action-per-tick: if any routine is due, the workflow runs the
44
+ // first one and exits. The pipeline-pick fallback only fires when
45
+ // *no* cron routine is due AND the caller asked for it (the trigger
46
+ // workflow does; ad-hoc CLI invocations don't, so they keep the old
47
+ // behaviour).
48
+ let nextAction = { kind: "noop" };
49
+ if (due.length > 0) {
50
+ const first = due[0];
51
+ nextAction = {
52
+ kind: "routine",
53
+ routine_id: first.routine_id,
54
+ window_key: first.window_key,
55
+ };
56
+ } else if (opts.pipelineFallback && token) {
57
+ const pick = await fetchPipelinePick(baseUrl, token, workspaceId, repoId, opts.event);
58
+ if (pick && pick.action === "pipeline_pick" && pick.specialist) {
59
+ nextAction = { kind: "pipeline_pick", specialist: pick.specialist };
60
+ }
61
+ }
62
+
30
63
  const result = {
31
64
  event: opts.event,
32
65
  status: due.length ? "due" : "noop",
@@ -34,18 +67,22 @@ export async function triggerCommand(ctx, rest) {
34
67
  due_lanes: dueLanesFromRoutines(due),
35
68
  skipped_routines: local.skipped,
36
69
  claim_status: claimStatus,
70
+ next_action: nextAction,
37
71
  };
38
72
 
39
73
  if (ctx.json || opts.json) {
40
74
  console.log(JSON.stringify(result, null, 2));
41
75
  return;
42
76
  }
43
- if (!due.length) {
44
- console.log(`Ship trigger ${opts.event}: no routines due.`);
77
+ if (nextAction.kind === "noop") {
78
+ console.log(`Ship trigger ${opts.event}: no action this tick.`);
79
+ return;
80
+ }
81
+ if (nextAction.kind === "routine") {
82
+ console.log(`Ship trigger ${opts.event}: routine ${nextAction.routine_id}`);
45
83
  return;
46
84
  }
47
- console.log(`Ship trigger ${opts.event}: ${due.length} routine(s) due`);
48
- for (const routine of due) console.log(` - ${routine.routine_id}`);
85
+ console.log(`Ship trigger ${opts.event}: pipeline pick → ${nextAction.specialist}`);
49
86
  }
50
87
 
51
88
  function explicitGlobalBaseUrl(ctx) {
@@ -53,13 +90,30 @@ function explicitGlobalBaseUrl(ctx) {
53
90
  }
54
91
 
55
92
  function printHelp() {
56
- console.log(`shipctl trigger — compute which routines are due (${VERSION})
93
+ console.log(`shipctl trigger — compute the single next Ship action for this tick (${VERSION})
57
94
 
58
95
  USAGE
59
- shipctl trigger --event schedule [--repo <id|owner/name>] [--workspace <id>] [--json]
96
+ shipctl trigger --event schedule [--repo <id|owner/name>] [--workspace <id>]
97
+ [--pipeline-fallback] [--json]
98
+
99
+ OUTPUT
100
+ 'next_action' carries the chosen work for this tick:
101
+ {"kind": "routine", "routine_id": ...} — a cron routine fired this tick
102
+ {"kind": "pipeline_pick", "specialist": ...} — no routine due; pipeline-pick chose a specialist
103
+ {"kind": "noop"} — nothing to do (and no fallback, or fallback empty)
104
+
105
+ 'due_routines' keeps the legacy multi-item shape for callers that
106
+ parse it directly; the trigger workflow only consumes 'next_action'.
107
+
108
+ FLAGS
109
+ --pipeline-fallback When no routine is due, call /pipeline-pick to
110
+ get a specialist to run instead. Default: off
111
+ (so ad-hoc CLI invocations don't write audit
112
+ log noise).
60
113
 
61
114
  ENV
62
115
  SHIP_API_TOKEN Optional. When set, due routines are claimed in Ship.
116
+ SHIP_WORKSPACE_ID Optional. Skips the /v1/workspaces lookup if set.
63
117
  SHIP_WORKSPACE_API_BASE Optional API base override.
64
118
  SHIP_API_BASE Fallback API base override.
65
119
  `);
@@ -74,6 +128,7 @@ function parseArgs(args) {
74
128
  cwd: null,
75
129
  now: null,
76
130
  noClaim: false,
131
+ pipelineFallback: false,
77
132
  json: false,
78
133
  };
79
134
  const copy = [...args];
@@ -107,6 +162,11 @@ function parseArgs(args) {
107
162
  copy.shift();
108
163
  continue;
109
164
  }
165
+ if (copy[0] === "--pipeline-fallback") {
166
+ out.pipelineFallback = true;
167
+ copy.shift();
168
+ continue;
169
+ }
110
170
  if (copy[0] === "--json") {
111
171
  out.json = true;
112
172
  copy.shift();
@@ -176,6 +236,31 @@ async function apiPostJson(baseUrl, path, body, token) {
176
236
  return apiRequest(baseUrl, path, "POST", token, body);
177
237
  }
178
238
 
239
+ async function fetchPipelinePick(baseUrl, token, workspaceId, repoId, event) {
240
+ try {
241
+ return await apiPostJson(
242
+ baseUrl,
243
+ `/v1/workspaces/${encodeURIComponent(workspaceId)}/repos/${encodeURIComponent(repoId)}/pipeline-pick`,
244
+ {
245
+ event,
246
+ github: {
247
+ event_name: process.env.SHIP_EVENT_NAME || process.env.GITHUB_EVENT_NAME || "",
248
+ ref: process.env.SHIP_REF || process.env.GITHUB_REF || "",
249
+ sha: process.env.SHIP_SHA || process.env.GITHUB_SHA || "",
250
+ run_id: process.env.GITHUB_RUN_ID || "",
251
+ },
252
+ },
253
+ token,
254
+ );
255
+ } catch (err) {
256
+ console.error(
257
+ `warn: pipeline-pick failed, no fallback this tick: ${err instanceof Error ? err.message : err}`,
258
+ );
259
+ return null;
260
+ }
261
+ }
262
+
263
+
179
264
  async function claimRoutine(baseUrl, token, workspaceId, repoId, event, routine) {
180
265
  try {
181
266
  return await apiPostJson(
@@ -1,14 +1,14 @@
1
1
  import { KNOWN_AGENTS } from "../detect.mjs";
2
2
 
3
3
  /* Historical schema used by every released shipctl through 0.11.x. We
4
- * keep validating it in parallel with v2 so customers who haven't run
5
- * `shipctl migrate` see clear warnings instead of silent failures. */
4
+ * keep validating it in parallel with v2 so legacy configs surface a
5
+ * deprecation warning instead of failing silently. */
6
6
  export const LEGACY_CONFIG_SCHEMA_VERSION = 1;
7
7
 
8
8
  /* RFC-0007 lanes-as-config. Introduced alongside `shipctl run`. Clients
9
9
  * that understand only v1 will refuse to read v2 and print a shipctl
10
10
  * upgrade hint; clients that understand v2 accept v1 with a deprecation
11
- * warning and suggest `shipctl migrate`. */
11
+ * warning. */
12
12
  export const CONFIG_SCHEMA_VERSION = 2;
13
13
 
14
14
  export const SUPPORTED_CONFIG_VERSIONS = Object.freeze([
@@ -65,6 +65,12 @@ export const PROCESS_TRIGGER_TYPES = Object.freeze(["manual", "event", "schedule
65
65
  * file names (`.github/workflows/ship-<lane>.yml`), and env vars, so
66
66
  * restrict them conservatively: ASCII lowercase, digits, dash, underscore. */
67
67
  export const LANE_ID_REGEX = /^[a-z0-9][a-z0-9_-]{0,63}$/;
68
+
69
+ /* Agent role slug — kebab-case only (Phase 2.4). Mirrors the
70
+ * server's ``backend/app/services/agent_roles.is_valid_slug`` regex
71
+ * so an invalid slug fails locally before the runtime resolver
72
+ * round-trips to the workspace API. Used for ``routine.specialist``. */
73
+ export const AGENT_ROLE_SLUG_REGEX = /^[a-z0-9][a-z0-9-]{0,63}$/;
68
74
  export const IDEMPOTENCY_KEY_REGEX = /^[a-z0-9][a-z0-9_.-]{0,127}$/;
69
75
  /* Coarse sanity check for 5-field crons: we're not a cron parser, but
70
76
  * anything that isn't whitespace-separated 5 tokens is almost certainly
@@ -115,11 +121,11 @@ export const PRESETS = Object.freeze([
115
121
 
116
122
  export const CHANNELS = Object.freeze(["stable", "edge"]);
117
123
 
118
- export const KINDS = Object.freeze(["pattern", "tool", "collection", "doc"]);
124
+ export const KINDS = Object.freeze(["collection"]);
119
125
 
120
126
  export const AGENT_IDS = Object.freeze(Object.keys(KNOWN_AGENTS));
121
127
 
122
- export const PIN_KEY_REGEX = /^(pattern|tool|collection|doc)\/[a-zA-Z0-9_\-\.\/]+$/;
128
+ export const PIN_KEY_REGEX = /^collection\/[a-zA-Z0-9_\-\.\/]+$/;
123
129
 
124
130
  export const UUID_V4_REGEX =
125
131
  /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@@ -330,7 +336,16 @@ function validateV2Process(obj, errors, warnings) {
330
336
  }
331
337
  pushUnknownKeyWarnings(
332
338
  process,
333
- new Set(["id", "name", "primary", "states", "transitions", "routines"]),
339
+ new Set([
340
+ "id",
341
+ "name",
342
+ "primary",
343
+ "states",
344
+ "transitions",
345
+ "routines",
346
+ "schedule",
347
+ "gates",
348
+ ]),
334
349
  "process",
335
350
  warnings,
336
351
  );
@@ -343,6 +358,19 @@ function validateV2Process(obj, errors, warnings) {
343
358
  if (process.primary !== undefined && typeof process.primary !== "boolean") {
344
359
  errors.push("process.primary: must be boolean when set");
345
360
  }
361
+ // Where the operator wants to interject (Phase 3). ``after_pr``
362
+ // (default) is fully autonomous through the agent reviewer;
363
+ // ``after_arch`` pauses for human approval after architects;
364
+ // ``after_ba`` pauses earlier, after BA writes the spec.
365
+ // Enforcement (FSM "needs review" routing) lands in Phase 3.5.
366
+ if (process.gates !== undefined) {
367
+ const allowed = new Set(["after_ba", "after_arch", "after_pr"]);
368
+ if (typeof process.gates !== "string" || !allowed.has(process.gates)) {
369
+ errors.push(
370
+ `process.gates: must be one of ${[...allowed].join(" | ")} when set`,
371
+ );
372
+ }
373
+ }
346
374
 
347
375
  if (!Array.isArray(process.states) || process.states.length < 1) {
348
376
  errors.push("process.states: must contain at least one state");
@@ -530,6 +558,10 @@ function validateProcessRoutines(value, errors, warnings) {
530
558
  "schedule",
531
559
  "window",
532
560
  "event",
561
+ // ``fsm_stage`` overrides the role's default stage so one role
562
+ // can drive multiple processes (BA serves both
563
+ // ``ba_requirements`` for SDLC and ``wbs`` for decomposition).
564
+ "fsm_stage",
533
565
  ]),
534
566
  prefix,
535
567
  warnings,
@@ -558,7 +590,37 @@ function validateProcessRoutines(value, errors, warnings) {
558
590
  }
559
591
  requireOptionalString(routine.specialist_id, `${prefix}.specialist_id`, errors, { required: false });
560
592
  requireOptionalString(routine.specialist_name, `${prefix}.specialist_name`, errors, { required: false });
593
+ /* ``routine.specialist`` is the canonical Phase 2.4 form — an
594
+ * agent role slug ``shipctl run`` resolves through
595
+ * ``GET /v1/.../agent-roles/{slug}/resolve``. Two shapes are
596
+ * accepted: a bare string (the slug) for the new spec, and an
597
+ * object (with ``id`` / ``name``) for the legacy process-state
598
+ * specialist record. Object form is validated elsewhere; here we
599
+ * only police the string form's slug grammar. */
600
+ if (typeof routine.specialist === "string") {
601
+ if (!AGENT_ROLE_SLUG_REGEX.test(routine.specialist)) {
602
+ errors.push(
603
+ `${prefix}.specialist: must be an agent-role slug (kebab-case [a-z0-9-]{1,64})`,
604
+ );
605
+ }
606
+ }
607
+ /* Legacy ``pattern: <slug>`` keeps working but nudges authors
608
+ * toward the new vocabulary. ``specialist: <slug>`` wins when
609
+ * both are set. */
610
+ if (
611
+ typeof routine.pattern === "string" &&
612
+ routine.pattern.trim() &&
613
+ typeof routine.specialist !== "string"
614
+ ) {
615
+ const suggested = routine.pattern.startsWith("role-")
616
+ ? routine.pattern.slice("role-".length)
617
+ : routine.pattern;
618
+ warnings.push(
619
+ `${prefix}.pattern: deprecated alias — use \`specialist: ${suggested}\` instead`,
620
+ );
621
+ }
561
622
  validateProcessAgentProfile(routine.agent_profile, `${prefix}.agent_profile`, errors);
623
+ requireOptionalString(routine.fsm_stage, `${prefix}.fsm_stage`, errors, { required: false });
562
624
  validateRoutineTrigger(routine.trigger, `${prefix}.trigger`, errors);
563
625
  if (routine.schedule !== undefined && routine.schedule !== null) {
564
626
  if (typeof routine.schedule !== "string" && !isPlainObject(routine.schedule)) {
@@ -639,9 +701,9 @@ function validateV2Lanes(obj, errors, warnings) {
639
701
 
640
702
  const lanes = obj.lanes;
641
703
  if (lanes === undefined) {
642
- /* An empty lanes map is legal — a fresh repo that has only gone
643
- * through `shipctl migrate` has no automation wired yet, and that's
644
- * the right default for onboarding. */
704
+ /* An empty lanes map is legal — a fresh repo seeded by the wizard
705
+ * declares automation under `process.routines:` instead, and the
706
+ * top-level `lanes:` key is left out. */
645
707
  return;
646
708
  }
647
709
  if (!isPlainObject(lanes)) {
@@ -863,7 +925,7 @@ export function validateConfig(obj) {
863
925
 
864
926
  if (!isV2) {
865
927
  warnings.push(
866
- `version: config is at v${obj.version}; run \`shipctl migrate\` to upgrade to v${CONFIG_SCHEMA_VERSION}`,
928
+ `version: config is at v${obj.version}; v${CONFIG_SCHEMA_VERSION} is the current schema. Re-seed the repo from the workspace wizard to upgrade.`,
867
929
  );
868
930
  }
869
931
 
@@ -944,7 +1006,7 @@ export function validateConfig(obj) {
944
1006
  for (const [k, v] of Object.entries(artifacts.pins)) {
945
1007
  if (!PIN_KEY_REGEX.test(k)) {
946
1008
  errors.push(
947
- `artifacts.pins[${JSON.stringify(k)}]: invalid key; expected <kind>/<id> where kind∈{pattern,tool,collection,doc}`,
1009
+ `artifacts.pins[${JSON.stringify(k)}]: invalid key; expected collection/<id>`,
948
1010
  );
949
1011
  }
950
1012
  if (typeof v !== "string" || !SEMVER_OR_RANGE_REGEX.test(v.trim())) {
package/lib/http.mjs CHANGED
@@ -93,8 +93,6 @@ export async function apiGet(baseUrl, path) {
93
93
  */
94
94
  export async function fetchManifest(baseUrl, { channel } = {}) {
95
95
  const KINDS = [
96
- { plural: "patterns", singular: "pattern" },
97
- { plural: "tools", singular: "tool" },
98
96
  { plural: "collections", singular: "collection" },
99
97
  ];
100
98
  const responses = await Promise.all(
@@ -47,11 +47,16 @@ export function executableIds(config) {
47
47
 
48
48
  export function routineToExecutable(id, routine) {
49
49
  const trigger = normalizeRoutineTrigger(routine);
50
+ // Phase 2.4: ``specialist:`` is the canonical agent-role slug;
51
+ // ``pattern:`` is kept as a back-compat alias and translated to a
52
+ // slug below (drop the ``role-`` prefix the legacy catalog used).
53
+ const specialist = pickSpecialistSlug(routine);
50
54
  return {
51
55
  id,
52
56
  type: "routine",
53
57
  kind: trigger.kind,
54
58
  trigger,
59
+ specialist,
55
60
  pattern: stringOrNull(routine.pattern),
56
61
  patterns: Array.isArray(routine.patterns) ? routine.patterns : undefined,
57
62
  pattern_version: stringOrNull(routine.pattern_version),
@@ -59,9 +64,43 @@ export function routineToExecutable(id, routine) {
59
64
  idempotency: routine.idempotency || null,
60
65
  prompt: stringOrNull(routine.prompt) || stringOrNull(routine.instructions),
61
66
  agent_profile: stringOrNull(routine.agent_profile) || stringOrNull(routine.specialist?.agent_profile),
67
+ // Per-routine FSM stage override. When set, ``shipctl run`` uses
68
+ // it instead of the role's default ``fsm_stage`` — that's how a
69
+ // single role (e.g. ``ba``) serves both SDLC (``ba_requirements``)
70
+ // and decomposition (``wbs``) without per-process role clones.
71
+ fsm_stage: stringOrNull(routine.fsm_stage),
62
72
  };
63
73
  }
64
74
 
75
+ /**
76
+ * Pull the agent-role slug out of a routine, preferring the new
77
+ * ``specialist:`` key but falling back to the legacy ``pattern:`` and
78
+ * stripping the historical ``role-`` prefix when present.
79
+ *
80
+ * Object-form ``specialist`` (the process-state record with ``id`` /
81
+ * ``name``) is treated as a slug source via ``specialist.id`` for
82
+ * configs that mirror the process-stage shape into routines.
83
+ */
84
+ function pickSpecialistSlug(routine) {
85
+ if (typeof routine.specialist === "string") {
86
+ const v = routine.specialist.trim();
87
+ if (v) return v;
88
+ }
89
+ if (
90
+ routine.specialist &&
91
+ typeof routine.specialist === "object" &&
92
+ typeof routine.specialist.id === "string"
93
+ ) {
94
+ const v = routine.specialist.id.trim();
95
+ if (v) return v;
96
+ }
97
+ if (typeof routine.pattern === "string" && routine.pattern.trim()) {
98
+ const p = routine.pattern.trim();
99
+ return p.startsWith("role-") ? p.slice("role-".length) : p;
100
+ }
101
+ return null;
102
+ }
103
+
65
104
  export function laneToExecutable(id, lane) {
66
105
  return {
67
106
  id,
package/lib/templates.mjs CHANGED
@@ -33,8 +33,8 @@ shipctl pattern list
33
33
  shipctl pattern show role-developer # resolves latest or pin
34
34
  shipctl pattern fetch role-developer --version 1.4.2
35
35
  shipctl search "release gates and qa split" --top-k 8
36
- shipctl docs fetch documentation/adoption/delivery-quality-and-release-process.md
37
- shipctl sync # reconcile .ship/cache/
36
+ shipctl knowledge fetch repository-context # workspace bucket
37
+ shipctl sync # reconcile .ship/cache/
38
38
  \`\`\`
39
39
 
40
40
  ## HTTP (curl, no CLI)
@@ -1,7 +1,4 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
1
  import { detectAgentTargets } from "../../detect.mjs";
4
- import { readCachedArtifact } from "../../cache/store.mjs";
5
2
 
6
3
  export const id = "agents-on-disk";
7
4
  export const category = "config";
@@ -17,32 +14,12 @@ export async function run(ctx) {
17
14
  if (!declared.length) {
18
15
  return { status: "skip", detail: "stack.agents is empty" };
19
16
  }
17
+ // Phase 2.5 retired the local artifact cache; agent-rule install
18
+ // paths are no longer available via cached frontmatter. Fall back
19
+ // to the heuristic detector only — it covers the common targets
20
+ // (CLAUDE.md, AGENTS.md, .cursor/rules/...) the seed PR writes.
20
21
  const detected = new Set(detectAgentTargets(ctx.cwd).map((t) => t.id));
21
- const missing = [];
22
- for (const agent of declared) {
23
- if (detected.has(agent)) continue;
24
- // Second chance: the cached agent-rules artifact may declare a custom
25
- // install_target (e.g. codex -> AGENTS.md) that the heuristic detector
26
- // doesn't recognise. Treat a present install_target file as "signal".
27
- let fm = null;
28
- try {
29
- fm = readCachedArtifact(ctx.cwd, "collection", `agent-rules-${agent}`);
30
- } catch {
31
- fm = null;
32
- }
33
- const topLevel = fm && fm.fm && typeof fm.fm.install_target === "string"
34
- ? fm.fm.install_target.trim()
35
- : "";
36
- const nested = fm && fm.spec && typeof fm.spec.install_target === "string"
37
- ? fm.spec.install_target.trim()
38
- : "";
39
- const target = topLevel || nested;
40
- if (target && fs.existsSync(path.join(ctx.cwd, target))) {
41
- detected.add(agent);
42
- continue;
43
- }
44
- missing.push(agent);
45
- }
22
+ const missing = declared.filter((agent) => !detected.has(agent));
46
23
  if (missing.length) {
47
24
  return {
48
25
  status: "warn",
@@ -23,32 +23,31 @@
23
23
  */
24
24
 
25
25
  import * as configPresent from "./checks/config-present.mjs";
26
- import * as gitignoreCache from "./checks/gitignore-cache.mjs";
27
- import * as rulesMarkers from "./checks/rules-markers.mjs";
28
- import * as cacheIntegrity from "./checks/cache-integrity.mjs";
29
26
  import * as bootstrapFiles from "./checks/bootstrap-files.mjs";
30
27
  import * as stackEnums from "./checks/stack-enums.mjs";
31
28
  import * as agentsOnDisk from "./checks/agents-on-disk.mjs";
32
29
  import * as apiReachable from "./checks/api-reachable.mjs";
33
- import * as artifactsUpToDate from "./checks/artifacts-up-to-date.mjs";
34
30
  import * as trackerLabels from "./checks/tracker-labels.mjs";
35
31
  import * as ciSecrets from "./checks/ci-secrets.mjs";
36
32
 
37
33
  /**
38
34
  * Ordered list of checks. Order governs how they appear in `shipctl verify`
39
35
  * output; within a category we keep a stable human-friendly grouping.
36
+ *
37
+ * Phase 2.5 retired four cache-coupled checks (``gitignore-cache``,
38
+ * ``rules-markers``, ``cache-integrity``, ``artifacts-up-to-date``)
39
+ * along with the ``shipctl sync`` flow that filled the cache. Agent
40
+ * rule files now live in the seed PR — there's nothing left in
41
+ * ``.ship/cache`` for ``verify`` to validate.
42
+ *
40
43
  * @type {Check[]}
41
44
  */
42
45
  const CHECKS = [
43
46
  configPresent,
44
- gitignoreCache,
45
47
  stackEnums,
46
- rulesMarkers,
47
- cacheIntegrity,
48
48
  bootstrapFiles,
49
49
  agentsOnDisk,
50
50
  apiReachable,
51
- artifactsUpToDate,
52
51
  trackerLabels,
53
52
  ciSecrets,
54
53
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elmundi/ship-cli",
3
- "version": "0.14.2",
3
+ "version": "0.15.4",
4
4
  "type": "module",
5
5
  "description": "Ship CLI: bootstrap a repo, sync the catalog, run process routines, report outcomes.",
6
6
  "license": "Apache-2.0",