shipwright-cli 3.2.0 → 3.3.0

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 (279) hide show
  1. package/.claude/agents/code-reviewer.md +2 -0
  2. package/.claude/agents/devops-engineer.md +2 -0
  3. package/.claude/agents/doc-fleet-agent.md +2 -0
  4. package/.claude/agents/pipeline-agent.md +2 -0
  5. package/.claude/agents/shell-script-specialist.md +2 -0
  6. package/.claude/agents/test-specialist.md +2 -0
  7. package/.claude/hooks/agent-crash-capture.sh +32 -0
  8. package/.claude/hooks/post-tool-use.sh +3 -2
  9. package/.claude/hooks/pre-tool-use.sh +35 -3
  10. package/README.md +4 -4
  11. package/claude-code/hooks/config-change.sh +18 -0
  12. package/claude-code/hooks/instructions-reloaded.sh +7 -0
  13. package/claude-code/hooks/worktree-create.sh +25 -0
  14. package/claude-code/hooks/worktree-remove.sh +20 -0
  15. package/config/code-constitution.json +130 -0
  16. package/dashboard/middleware/auth.ts +134 -0
  17. package/dashboard/middleware/constants.ts +21 -0
  18. package/dashboard/public/index.html +2 -6
  19. package/dashboard/public/styles.css +100 -97
  20. package/dashboard/routes/auth.ts +38 -0
  21. package/dashboard/server.ts +66 -25
  22. package/dashboard/services/config.ts +26 -0
  23. package/dashboard/services/db.ts +118 -0
  24. package/dashboard/src/canvas/pixel-agent.ts +298 -0
  25. package/dashboard/src/canvas/pixel-sprites.ts +440 -0
  26. package/dashboard/src/canvas/shipyard-effects.ts +367 -0
  27. package/dashboard/src/canvas/shipyard-scene.ts +616 -0
  28. package/dashboard/src/canvas/submarine-layout.ts +267 -0
  29. package/dashboard/src/components/header.ts +8 -7
  30. package/dashboard/src/core/router.ts +1 -0
  31. package/dashboard/src/design/submarine-theme.ts +253 -0
  32. package/dashboard/src/main.ts +2 -0
  33. package/dashboard/src/types/api.ts +2 -1
  34. package/dashboard/src/views/activity.ts +2 -1
  35. package/dashboard/src/views/shipyard.ts +39 -0
  36. package/dashboard/types/index.ts +166 -0
  37. package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
  38. package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
  39. package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
  40. package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
  41. package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
  42. package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
  43. package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
  44. package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
  45. package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
  46. package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
  47. package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
  48. package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
  49. package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
  50. package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
  51. package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
  52. package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
  53. package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
  54. package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
  55. package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
  56. package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
  57. package/docs/research/RESEARCH_INDEX.md +439 -0
  58. package/docs/research/RESEARCH_SOURCES.md +440 -0
  59. package/docs/research/RESEARCH_SUMMARY.txt +275 -0
  60. package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
  61. package/package.json +2 -2
  62. package/scripts/lib/adaptive-model.sh +427 -0
  63. package/scripts/lib/adaptive-timeout.sh +316 -0
  64. package/scripts/lib/audit-trail.sh +309 -0
  65. package/scripts/lib/auto-recovery.sh +471 -0
  66. package/scripts/lib/bandit-selector.sh +431 -0
  67. package/scripts/lib/bootstrap.sh +104 -2
  68. package/scripts/lib/causal-graph.sh +455 -0
  69. package/scripts/lib/compat.sh +126 -0
  70. package/scripts/lib/compound-audit.sh +337 -0
  71. package/scripts/lib/constitutional.sh +454 -0
  72. package/scripts/lib/context-budget.sh +359 -0
  73. package/scripts/lib/convergence.sh +594 -0
  74. package/scripts/lib/cost-optimizer.sh +634 -0
  75. package/scripts/lib/daemon-adaptive.sh +10 -0
  76. package/scripts/lib/daemon-dispatch.sh +106 -17
  77. package/scripts/lib/daemon-failure.sh +34 -4
  78. package/scripts/lib/daemon-patrol.sh +23 -2
  79. package/scripts/lib/daemon-poll-github.sh +361 -0
  80. package/scripts/lib/daemon-poll-health.sh +299 -0
  81. package/scripts/lib/daemon-poll.sh +27 -611
  82. package/scripts/lib/daemon-state.sh +112 -66
  83. package/scripts/lib/daemon-triage.sh +10 -0
  84. package/scripts/lib/dod-scorecard.sh +442 -0
  85. package/scripts/lib/error-actionability.sh +300 -0
  86. package/scripts/lib/formal-spec.sh +461 -0
  87. package/scripts/lib/helpers.sh +177 -4
  88. package/scripts/lib/intent-analysis.sh +409 -0
  89. package/scripts/lib/loop-convergence.sh +350 -0
  90. package/scripts/lib/loop-iteration.sh +682 -0
  91. package/scripts/lib/loop-progress.sh +48 -0
  92. package/scripts/lib/loop-restart.sh +185 -0
  93. package/scripts/lib/memory-effectiveness.sh +506 -0
  94. package/scripts/lib/mutation-executor.sh +352 -0
  95. package/scripts/lib/outcome-feedback.sh +521 -0
  96. package/scripts/lib/pipeline-cli.sh +336 -0
  97. package/scripts/lib/pipeline-commands.sh +1216 -0
  98. package/scripts/lib/pipeline-detection.sh +100 -2
  99. package/scripts/lib/pipeline-execution.sh +897 -0
  100. package/scripts/lib/pipeline-github.sh +28 -3
  101. package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
  102. package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
  103. package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
  104. package/scripts/lib/pipeline-intelligence.sh +100 -1136
  105. package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
  106. package/scripts/lib/pipeline-quality-checks.sh +17 -715
  107. package/scripts/lib/pipeline-quality-gates.sh +563 -0
  108. package/scripts/lib/pipeline-stages-build.sh +730 -0
  109. package/scripts/lib/pipeline-stages-delivery.sh +965 -0
  110. package/scripts/lib/pipeline-stages-intake.sh +1133 -0
  111. package/scripts/lib/pipeline-stages-monitor.sh +407 -0
  112. package/scripts/lib/pipeline-stages-review.sh +1022 -0
  113. package/scripts/lib/pipeline-stages.sh +59 -2929
  114. package/scripts/lib/pipeline-state.sh +36 -5
  115. package/scripts/lib/pipeline-util.sh +487 -0
  116. package/scripts/lib/policy-learner.sh +438 -0
  117. package/scripts/lib/process-reward.sh +493 -0
  118. package/scripts/lib/project-detect.sh +649 -0
  119. package/scripts/lib/quality-profile.sh +334 -0
  120. package/scripts/lib/recruit-commands.sh +885 -0
  121. package/scripts/lib/recruit-learning.sh +739 -0
  122. package/scripts/lib/recruit-roles.sh +648 -0
  123. package/scripts/lib/reward-aggregator.sh +458 -0
  124. package/scripts/lib/rl-optimizer.sh +362 -0
  125. package/scripts/lib/root-cause.sh +427 -0
  126. package/scripts/lib/scope-enforcement.sh +445 -0
  127. package/scripts/lib/session-restart.sh +493 -0
  128. package/scripts/lib/skill-memory.sh +300 -0
  129. package/scripts/lib/skill-registry.sh +775 -0
  130. package/scripts/lib/spec-driven.sh +476 -0
  131. package/scripts/lib/test-helpers.sh +18 -7
  132. package/scripts/lib/test-holdout.sh +429 -0
  133. package/scripts/lib/test-optimizer.sh +511 -0
  134. package/scripts/shipwright-file-suggest.sh +45 -0
  135. package/scripts/skills/adversarial-quality.md +61 -0
  136. package/scripts/skills/api-design.md +44 -0
  137. package/scripts/skills/architecture-design.md +50 -0
  138. package/scripts/skills/brainstorming.md +43 -0
  139. package/scripts/skills/data-pipeline.md +44 -0
  140. package/scripts/skills/deploy-safety.md +64 -0
  141. package/scripts/skills/documentation.md +38 -0
  142. package/scripts/skills/frontend-design.md +45 -0
  143. package/scripts/skills/generated/.gitkeep +0 -0
  144. package/scripts/skills/generated/_refinements/.gitkeep +0 -0
  145. package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
  146. package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
  147. package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
  148. package/scripts/skills/generated/cli-version-management.md +29 -0
  149. package/scripts/skills/generated/collection-system-validation.md +99 -0
  150. package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
  151. package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
  152. package/scripts/skills/generated/test-parallelization-detection.md +65 -0
  153. package/scripts/skills/observability.md +79 -0
  154. package/scripts/skills/performance.md +48 -0
  155. package/scripts/skills/pr-quality.md +49 -0
  156. package/scripts/skills/product-thinking.md +43 -0
  157. package/scripts/skills/security-audit.md +49 -0
  158. package/scripts/skills/systematic-debugging.md +40 -0
  159. package/scripts/skills/testing-strategy.md +47 -0
  160. package/scripts/skills/two-stage-review.md +52 -0
  161. package/scripts/skills/validation-thoroughness.md +55 -0
  162. package/scripts/sw +9 -3
  163. package/scripts/sw-activity.sh +9 -2
  164. package/scripts/sw-adaptive.sh +2 -1
  165. package/scripts/sw-adversarial.sh +2 -1
  166. package/scripts/sw-architecture-enforcer.sh +3 -1
  167. package/scripts/sw-auth.sh +12 -2
  168. package/scripts/sw-autonomous.sh +5 -1
  169. package/scripts/sw-changelog.sh +4 -1
  170. package/scripts/sw-checkpoint.sh +2 -1
  171. package/scripts/sw-ci.sh +5 -1
  172. package/scripts/sw-cleanup.sh +4 -26
  173. package/scripts/sw-code-review.sh +10 -4
  174. package/scripts/sw-connect.sh +2 -1
  175. package/scripts/sw-context.sh +2 -1
  176. package/scripts/sw-cost.sh +48 -3
  177. package/scripts/sw-daemon.sh +66 -9
  178. package/scripts/sw-dashboard.sh +3 -1
  179. package/scripts/sw-db.sh +59 -16
  180. package/scripts/sw-decide.sh +8 -2
  181. package/scripts/sw-decompose.sh +360 -17
  182. package/scripts/sw-deps.sh +4 -1
  183. package/scripts/sw-developer-simulation.sh +4 -1
  184. package/scripts/sw-discovery.sh +325 -2
  185. package/scripts/sw-doc-fleet.sh +4 -1
  186. package/scripts/sw-docs-agent.sh +3 -1
  187. package/scripts/sw-docs.sh +2 -1
  188. package/scripts/sw-doctor.sh +453 -2
  189. package/scripts/sw-dora.sh +4 -1
  190. package/scripts/sw-durable.sh +4 -3
  191. package/scripts/sw-e2e-orchestrator.sh +17 -16
  192. package/scripts/sw-eventbus.sh +7 -1
  193. package/scripts/sw-evidence.sh +364 -12
  194. package/scripts/sw-feedback.sh +550 -9
  195. package/scripts/sw-fix.sh +20 -1
  196. package/scripts/sw-fleet-discover.sh +6 -2
  197. package/scripts/sw-fleet-viz.sh +4 -1
  198. package/scripts/sw-fleet.sh +5 -1
  199. package/scripts/sw-github-app.sh +16 -3
  200. package/scripts/sw-github-checks.sh +3 -2
  201. package/scripts/sw-github-deploy.sh +3 -2
  202. package/scripts/sw-github-graphql.sh +18 -7
  203. package/scripts/sw-guild.sh +5 -1
  204. package/scripts/sw-heartbeat.sh +5 -30
  205. package/scripts/sw-hello.sh +67 -0
  206. package/scripts/sw-hygiene.sh +6 -1
  207. package/scripts/sw-incident.sh +265 -1
  208. package/scripts/sw-init.sh +18 -2
  209. package/scripts/sw-instrument.sh +10 -2
  210. package/scripts/sw-intelligence.sh +42 -6
  211. package/scripts/sw-jira.sh +5 -1
  212. package/scripts/sw-launchd.sh +2 -1
  213. package/scripts/sw-linear.sh +4 -1
  214. package/scripts/sw-logs.sh +4 -1
  215. package/scripts/sw-loop.sh +432 -1128
  216. package/scripts/sw-memory.sh +356 -2
  217. package/scripts/sw-mission-control.sh +6 -1
  218. package/scripts/sw-model-router.sh +481 -26
  219. package/scripts/sw-otel.sh +13 -4
  220. package/scripts/sw-oversight.sh +14 -5
  221. package/scripts/sw-patrol-meta.sh +334 -0
  222. package/scripts/sw-pipeline-composer.sh +5 -1
  223. package/scripts/sw-pipeline-vitals.sh +2 -1
  224. package/scripts/sw-pipeline.sh +53 -2664
  225. package/scripts/sw-pm.sh +12 -5
  226. package/scripts/sw-pr-lifecycle.sh +2 -1
  227. package/scripts/sw-predictive.sh +7 -1
  228. package/scripts/sw-prep.sh +185 -2
  229. package/scripts/sw-ps.sh +5 -25
  230. package/scripts/sw-public-dashboard.sh +15 -3
  231. package/scripts/sw-quality.sh +2 -1
  232. package/scripts/sw-reaper.sh +8 -25
  233. package/scripts/sw-recruit.sh +156 -2303
  234. package/scripts/sw-regression.sh +19 -12
  235. package/scripts/sw-release-manager.sh +3 -1
  236. package/scripts/sw-release.sh +4 -1
  237. package/scripts/sw-remote.sh +3 -1
  238. package/scripts/sw-replay.sh +7 -1
  239. package/scripts/sw-retro.sh +158 -1
  240. package/scripts/sw-review-rerun.sh +3 -1
  241. package/scripts/sw-scale.sh +10 -3
  242. package/scripts/sw-security-audit.sh +6 -1
  243. package/scripts/sw-self-optimize.sh +6 -3
  244. package/scripts/sw-session.sh +9 -3
  245. package/scripts/sw-setup.sh +3 -1
  246. package/scripts/sw-stall-detector.sh +406 -0
  247. package/scripts/sw-standup.sh +15 -7
  248. package/scripts/sw-status.sh +3 -1
  249. package/scripts/sw-strategic.sh +4 -1
  250. package/scripts/sw-stream.sh +7 -1
  251. package/scripts/sw-swarm.sh +18 -6
  252. package/scripts/sw-team-stages.sh +13 -6
  253. package/scripts/sw-templates.sh +5 -29
  254. package/scripts/sw-testgen.sh +7 -1
  255. package/scripts/sw-tmux-pipeline.sh +4 -1
  256. package/scripts/sw-tmux-role-color.sh +2 -0
  257. package/scripts/sw-tmux-status.sh +1 -1
  258. package/scripts/sw-tmux.sh +3 -1
  259. package/scripts/sw-trace.sh +3 -1
  260. package/scripts/sw-tracker-github.sh +3 -0
  261. package/scripts/sw-tracker-jira.sh +3 -0
  262. package/scripts/sw-tracker-linear.sh +3 -0
  263. package/scripts/sw-tracker.sh +3 -1
  264. package/scripts/sw-triage.sh +2 -1
  265. package/scripts/sw-upgrade.sh +3 -1
  266. package/scripts/sw-ux.sh +5 -2
  267. package/scripts/sw-webhook.sh +3 -1
  268. package/scripts/sw-widgets.sh +3 -1
  269. package/scripts/sw-worktree.sh +15 -3
  270. package/scripts/test-skill-injection.sh +1233 -0
  271. package/templates/pipelines/autonomous.json +27 -3
  272. package/templates/pipelines/cost-aware.json +34 -8
  273. package/templates/pipelines/deployed.json +12 -0
  274. package/templates/pipelines/enterprise.json +12 -0
  275. package/templates/pipelines/fast.json +6 -0
  276. package/templates/pipelines/full.json +27 -3
  277. package/templates/pipelines/hotfix.json +6 -0
  278. package/templates/pipelines/standard.json +12 -0
  279. package/templates/pipelines/tdd.json +12 -0
@@ -0,0 +1,26 @@
1
+ // Centralized configuration for dashboard services
2
+ import { join } from "path";
3
+
4
+ const HOME = process.env.HOME || "";
5
+
6
+ export const config = {
7
+ port: parseInt(
8
+ process.argv[2] || process.env.SHIPWRIGHT_DASHBOARD_PORT || "8767",
9
+ ),
10
+ home: HOME,
11
+ eventsFile: join(HOME, ".shipwright", "events.jsonl"),
12
+ daemonState: join(HOME, ".shipwright", "daemon-state.json"),
13
+ logsDir: join(HOME, ".shipwright", "logs"),
14
+ heartbeatDir: join(HOME, ".shipwright", "heartbeats"),
15
+ machinesFile: join(HOME, ".shipwright", "machines.json"),
16
+ costsFile: join(HOME, ".shipwright", "costs.json"),
17
+ budgetFile: join(HOME, ".shipwright", "budget.json"),
18
+ memoryDir: join(HOME, ".shipwright", "memory"),
19
+ publicDir: join(import.meta.dir, "../public"),
20
+ dbFile: join(HOME, ".shipwright", "shipwright.db"),
21
+ sessionsFile: join(HOME, ".shipwright", "sessions.json"),
22
+ developerRegistryFile: join(HOME, ".shipwright", "developer-registry.json"),
23
+ teamEventsFile: join(HOME, ".shipwright", "team-events.jsonl"),
24
+ inviteTokensFile: join(HOME, ".shipwright", "invite-tokens.json"),
25
+ notificationsConfigFile: join(HOME, ".shipwright", "notifications.json"),
26
+ };
@@ -0,0 +1,118 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { existsSync } from "fs";
3
+ import { config } from "./config.js";
4
+ import type { DaemonEvent } from "../types/index.js";
5
+
6
+ let db: Database | null = null;
7
+
8
+ export function getDb(): Database | null {
9
+ if (db) return db;
10
+ try {
11
+ if (!existsSync(config.dbFile)) return null;
12
+ db = new Database(config.dbFile, { readonly: true });
13
+ db.exec("PRAGMA journal_mode=WAL;");
14
+ return db;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ export function dbQueryEventsByIdGreaterThan(
21
+ afterId: number,
22
+ limit = 100,
23
+ ): Array<Record<string, unknown>> {
24
+ const conn = getDb();
25
+ if (!conn) return [];
26
+ try {
27
+ return conn
28
+ .query(`SELECT * FROM events WHERE id > ? ORDER BY id ASC LIMIT ?`)
29
+ .all(afterId, limit) as Array<Record<string, unknown>>;
30
+ } catch {
31
+ return [];
32
+ }
33
+ }
34
+
35
+ export function dbQueryEvents(since?: number, limit = 200): DaemonEvent[] {
36
+ const conn = getDb();
37
+ if (!conn) return [];
38
+ try {
39
+ const cutoff = since || 0;
40
+ const rows = conn
41
+ .query(
42
+ `SELECT ts, ts_epoch, type, job_id, stage, status, duration_secs, metadata
43
+ FROM events WHERE ts_epoch >= ? ORDER BY ts_epoch DESC LIMIT ?`,
44
+ )
45
+ .all(cutoff, limit) as Array<Record<string, unknown>>;
46
+ return rows.map((r) => {
47
+ const base: DaemonEvent = {
48
+ ts: r.ts as string,
49
+ ts_epoch: r.ts_epoch as number,
50
+ type: r.type as string,
51
+ };
52
+ if (r.job_id)
53
+ base.issue = parseInt(String(r.job_id).replace(/\D/g, "")) || undefined;
54
+ if (r.stage) base.stage = r.stage as string;
55
+ if (r.duration_secs) base.duration_s = r.duration_secs as number;
56
+ if (r.status) base.result = r.status as string;
57
+ if (r.metadata) {
58
+ try {
59
+ Object.assign(base, JSON.parse(r.metadata as string));
60
+ } catch {
61
+ /* ignore malformed metadata */
62
+ }
63
+ }
64
+ return base;
65
+ });
66
+ } catch {
67
+ return [];
68
+ }
69
+ }
70
+
71
+ export function dbQueryJobs(status?: string): Array<Record<string, unknown>> {
72
+ const conn = getDb();
73
+ if (!conn) return [];
74
+ try {
75
+ if (status) {
76
+ return conn
77
+ .query(
78
+ "SELECT * FROM daemon_state WHERE status = ? ORDER BY started_at DESC",
79
+ )
80
+ .all(status) as Array<Record<string, unknown>>;
81
+ }
82
+ return conn
83
+ .query("SELECT * FROM daemon_state ORDER BY started_at DESC LIMIT 50")
84
+ .all() as Array<Record<string, unknown>>;
85
+ } catch {
86
+ return [];
87
+ }
88
+ }
89
+
90
+ export function dbQueryCostsToday(): { total: number; count: number } {
91
+ const conn = getDb();
92
+ if (!conn) return { total: 0, count: 0 };
93
+ try {
94
+ const todayStart = new Date();
95
+ todayStart.setUTCHours(0, 0, 0, 0);
96
+ const epoch = Math.floor(todayStart.getTime() / 1000);
97
+ const row = conn
98
+ .query(
99
+ "SELECT COALESCE(SUM(cost_usd), 0) as total, COUNT(*) as count FROM cost_entries WHERE ts_epoch >= ?",
100
+ )
101
+ .get(epoch) as { total: number; count: number } | null;
102
+ return row || { total: 0, count: 0 };
103
+ } catch {
104
+ return { total: 0, count: 0 };
105
+ }
106
+ }
107
+
108
+ export function dbQueryHeartbeats(): Array<Record<string, unknown>> {
109
+ const conn = getDb();
110
+ if (!conn) return [];
111
+ try {
112
+ return conn
113
+ .query("SELECT * FROM heartbeats ORDER BY updated_at DESC")
114
+ .all() as Array<Record<string, unknown>>;
115
+ } catch {
116
+ return [];
117
+ }
118
+ }
@@ -0,0 +1,298 @@
1
+ // Pixel Agent State Machine — Submarine crew members animated across compartments
2
+
3
+ import type { StageName } from "../design/tokens";
4
+ import type { CrewRole } from "../design/submarine-theme";
5
+ import { timing } from "../design/submarine-theme";
6
+ import { crewRoleForStage } from "../design/submarine-theme";
7
+ import type { SubmarineLayout } from "./submarine-layout";
8
+
9
+ // ── Types ────────────────────────────────────────────────────────────────────
10
+
11
+ export type AgentState =
12
+ | "spawn"
13
+ | "idle"
14
+ | "walk"
15
+ | "working"
16
+ | "alert"
17
+ | "despawn";
18
+
19
+ export type SpriteAction = "idle" | "walk" | "work" | "alert";
20
+
21
+ // ── PixelAgent class ────────────────────────────────────────────────────────
22
+
23
+ export class PixelAgent {
24
+ // Identity
25
+ issue: number;
26
+ stage: StageName;
27
+ role: CrewRole;
28
+
29
+ // Position & movement
30
+ x: number;
31
+ y: number;
32
+ targetX: number;
33
+ targetY: number;
34
+ direction: "left" | "right" = "right";
35
+ path: Point[] = [];
36
+ pathIndex: number = 0;
37
+
38
+ // Animation state
39
+ state: AgentState = "spawn";
40
+ stateTime: number = 0;
41
+ frameIndex: number = 0;
42
+ frameTimer: number = 0;
43
+
44
+ // Spawn/despawn progress (0-1)
45
+ spawnProgress: number = 0;
46
+
47
+ // Idle behavior
48
+ idleBobOffset: number = 0;
49
+ nextWanderTime: number = 0;
50
+
51
+ // Pipeline data
52
+ elapsed_s: number = 0;
53
+ iteration: number = 0;
54
+ status: string = "idle";
55
+
56
+ constructor(
57
+ issue: number,
58
+ stage: StageName,
59
+ role: CrewRole,
60
+ x: number,
61
+ y: number,
62
+ ) {
63
+ this.issue = issue;
64
+ this.stage = stage;
65
+ this.role = role;
66
+ this.x = x;
67
+ this.y = y;
68
+ this.targetX = x;
69
+ this.targetY = y;
70
+ this.nextWanderTime =
71
+ Math.random() * (timing.idleWanderRange[1] - timing.idleWanderRange[0]) +
72
+ timing.idleWanderRange[0];
73
+ }
74
+
75
+ update(dt: number, layout: SubmarineLayout): void {
76
+ this.stateTime += dt;
77
+ this.frameTimer += dt;
78
+
79
+ switch (this.state) {
80
+ case "spawn":
81
+ this.spawnProgress = Math.min(
82
+ 1,
83
+ this.spawnProgress + dt / timing.spawnDuration,
84
+ );
85
+ if (this.spawnProgress >= 1) {
86
+ this.state = "idle";
87
+ this.stateTime = 0;
88
+ }
89
+ break;
90
+
91
+ case "idle":
92
+ this.updateIdleState(dt, layout);
93
+ break;
94
+
95
+ case "walk":
96
+ this.updateWalkState(dt);
97
+ break;
98
+
99
+ case "working":
100
+ this.updateWorkingState(dt);
101
+ break;
102
+
103
+ case "alert":
104
+ this.updateAlertState(dt);
105
+ break;
106
+
107
+ case "despawn":
108
+ this.spawnProgress = Math.max(
109
+ 0,
110
+ this.spawnProgress - dt / timing.despawnDuration,
111
+ );
112
+ break;
113
+ }
114
+ }
115
+
116
+ private updateIdleState(dt: number, layout: SubmarineLayout): void {
117
+ // Idle bob animation
118
+ const bobFreq = (2 * Math.PI) / timing.idleBobPeriod;
119
+ this.idleBobOffset =
120
+ Math.sin(this.stateTime * bobFreq) * timing.idleBobAmplitude;
121
+
122
+ // Advance idle frame
123
+ const idleFrameInterval = 0.5;
124
+ if (this.frameTimer >= idleFrameInterval) {
125
+ this.frameIndex = (this.frameIndex + 1) % 2;
126
+ this.frameTimer = 0;
127
+ }
128
+
129
+ // Occasional wander
130
+ this.nextWanderTime -= dt;
131
+ if (this.nextWanderTime <= 0) {
132
+ const wanderDistance = 20;
133
+ const wanderX = this.x + (Math.random() - 0.5) * wanderDistance;
134
+ const wanderY = this.y + (Math.random() - 0.5) * wanderDistance;
135
+ this.targetX = wanderX;
136
+ this.targetY = wanderY;
137
+
138
+ this.nextWanderTime =
139
+ Math.random() *
140
+ (timing.idleWanderRange[1] - timing.idleWanderRange[0]) +
141
+ timing.idleWanderRange[0];
142
+ }
143
+ }
144
+
145
+ private updateWalkState(dt: number): void {
146
+ if (this.path.length === 0) {
147
+ this.state = "working";
148
+ this.stateTime = 0;
149
+ this.frameIndex = 0;
150
+ this.frameTimer = 0;
151
+ return;
152
+ }
153
+
154
+ const target = this.path[this.pathIndex];
155
+ const dx = target.x - this.x;
156
+ const dy = target.y - this.y;
157
+ const distance = Math.sqrt(dx * dx + dy * dy);
158
+
159
+ // Update direction
160
+ if (dx !== 0) {
161
+ this.direction = dx > 0 ? "right" : "left";
162
+ }
163
+
164
+ if (distance < 2) {
165
+ // Reached waypoint, move to next
166
+ this.pathIndex++;
167
+ if (this.pathIndex >= this.path.length) {
168
+ this.x = target.x;
169
+ this.y = target.y;
170
+ this.state = "working";
171
+ this.stateTime = 0;
172
+ this.frameIndex = 0;
173
+ this.frameTimer = 0;
174
+ }
175
+ return;
176
+ }
177
+
178
+ // Move toward target
179
+ const moveDistance = Math.min(distance, timing.walkSpeed * dt);
180
+ const moveRatio = moveDistance / distance;
181
+ this.x += dx * moveRatio;
182
+ this.y += dy * moveRatio;
183
+
184
+ // Advance walk frame
185
+ const walkFrameInterval = 0.15;
186
+ if (this.frameTimer >= walkFrameInterval) {
187
+ this.frameIndex = (this.frameIndex + 1) % 4;
188
+ this.frameTimer = 0;
189
+ }
190
+ }
191
+
192
+ private updateWorkingState(dt: number): void {
193
+ // Work animation: 2 frames at 0.3s interval
194
+ const workFrameInterval = timing.workFrameInterval;
195
+ if (this.frameTimer >= workFrameInterval) {
196
+ this.frameIndex = (this.frameIndex + 1) % 2;
197
+ this.frameTimer = 0;
198
+ }
199
+ }
200
+
201
+ private updateAlertState(dt: number): void {
202
+ // Alert flash: frame 0 for 0.4s, frame 1 for 0.4s
203
+ const flashInterval = timing.alertFlashInterval;
204
+ const cycle = (this.stateTime % (flashInterval * 2)) / flashInterval;
205
+ this.frameIndex = cycle < 1 ? 0 : 1;
206
+ }
207
+
208
+ moveTo(stage: StageName, layout: SubmarineLayout): void {
209
+ const targetComp = layout.getCompartment(stage);
210
+ if (!targetComp) return;
211
+
212
+ const currentComp = layout.getCompartment(this.stage);
213
+ if (!currentComp) return;
214
+
215
+ this.path = layout.getPathBetween(this.stage, stage);
216
+ if (this.path.length === 0) return;
217
+
218
+ this.stage = stage;
219
+ this.role = crewRoleForStage(stage);
220
+ this.targetX = targetComp.stationX;
221
+ this.targetY = targetComp.stationY;
222
+ this.pathIndex = 0;
223
+ this.state = "walk";
224
+ this.stateTime = 0;
225
+ this.frameIndex = 0;
226
+ this.frameTimer = 0;
227
+ }
228
+
229
+ setAlert(): void {
230
+ this.state = "alert";
231
+ this.stateTime = 0;
232
+ this.frameIndex = 0;
233
+ this.frameTimer = 0;
234
+ }
235
+
236
+ setDespawn(): void {
237
+ this.state = "despawn";
238
+ this.spawnProgress = 1;
239
+ }
240
+
241
+ syncFromPipeline(
242
+ pipeline: {
243
+ stage: string;
244
+ elapsed_s: number;
245
+ iteration: number;
246
+ status: string;
247
+ },
248
+ layout: SubmarineLayout,
249
+ ): void {
250
+ const newStage = pipeline.stage as StageName;
251
+ const stageChanged = this.stage !== newStage;
252
+
253
+ this.elapsed_s = pipeline.elapsed_s;
254
+ this.iteration = pipeline.iteration;
255
+ this.status = pipeline.status;
256
+
257
+ if (stageChanged) {
258
+ this.moveTo(newStage, layout);
259
+ }
260
+
261
+ if (pipeline.status === "failed") {
262
+ this.setAlert();
263
+ }
264
+ }
265
+
266
+ isDead(): boolean {
267
+ return this.state === "despawn" && this.spawnProgress <= 0;
268
+ }
269
+
270
+ getSpriteAction(): SpriteAction {
271
+ switch (this.state) {
272
+ case "spawn":
273
+ case "idle":
274
+ return "idle";
275
+ case "walk":
276
+ return "walk";
277
+ case "working":
278
+ return "work";
279
+ case "alert":
280
+ case "despawn":
281
+ return "alert";
282
+ }
283
+ }
284
+
285
+ getSpriteFrame(): number {
286
+ return this.frameIndex;
287
+ }
288
+
289
+ getDisplayY(): number {
290
+ return this.y + this.idleBobOffset;
291
+ }
292
+ }
293
+
294
+ // Re-export Point type from layout
295
+ export interface Point {
296
+ x: number;
297
+ y: number;
298
+ }