ridgeline 0.7.5 → 0.7.12

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 (110) hide show
  1. package/README.md +105 -14
  2. package/dist/agents/core/designer.md +18 -0
  3. package/dist/agents/core/planner.md +32 -0
  4. package/dist/agents/core/retrospective.md +64 -0
  5. package/dist/catalog/build-catalog.d.ts +21 -0
  6. package/dist/catalog/build-catalog.js +305 -0
  7. package/dist/catalog/build-catalog.js.map +1 -0
  8. package/dist/catalog/classify.d.ts +16 -0
  9. package/dist/catalog/classify.js +189 -0
  10. package/dist/catalog/classify.js.map +1 -0
  11. package/dist/catalog/extract-metadata.d.ts +41 -0
  12. package/dist/catalog/extract-metadata.js +175 -0
  13. package/dist/catalog/extract-metadata.js.map +1 -0
  14. package/dist/catalog/pack-sprites.d.ts +8 -0
  15. package/dist/catalog/pack-sprites.js +106 -0
  16. package/dist/catalog/pack-sprites.js.map +1 -0
  17. package/dist/catalog/parse-conventions.d.ts +25 -0
  18. package/dist/catalog/parse-conventions.js +86 -0
  19. package/dist/catalog/parse-conventions.js.map +1 -0
  20. package/dist/catalog/resolve-asset-dir.d.ts +15 -0
  21. package/dist/catalog/resolve-asset-dir.js +96 -0
  22. package/dist/catalog/resolve-asset-dir.js.map +1 -0
  23. package/dist/catalog/types.d.ts +74 -0
  24. package/dist/catalog/types.js +3 -0
  25. package/dist/catalog/types.js.map +1 -0
  26. package/dist/catalog/vision-describe.d.ts +12 -0
  27. package/dist/catalog/vision-describe.js +158 -0
  28. package/dist/catalog/vision-describe.js.map +1 -0
  29. package/dist/cli.js +54 -1
  30. package/dist/cli.js.map +1 -1
  31. package/dist/commands/build.d.ts +2 -2
  32. package/dist/commands/build.js +156 -30
  33. package/dist/commands/build.js.map +1 -1
  34. package/dist/commands/catalog.d.ts +6 -0
  35. package/dist/commands/catalog.js +141 -0
  36. package/dist/commands/catalog.js.map +1 -0
  37. package/dist/commands/design.js +73 -0
  38. package/dist/commands/design.js.map +1 -1
  39. package/dist/commands/retrospective.d.ts +7 -0
  40. package/dist/commands/retrospective.js +119 -0
  41. package/dist/commands/retrospective.js.map +1 -0
  42. package/dist/config.js +1 -0
  43. package/dist/config.js.map +1 -1
  44. package/dist/engine/discovery/agent.registry.d.ts +3 -0
  45. package/dist/engine/discovery/agent.registry.js +15 -2
  46. package/dist/engine/discovery/agent.registry.js.map +1 -1
  47. package/dist/engine/pipeline/build.exec.d.ts +1 -1
  48. package/dist/engine/pipeline/build.exec.js +24 -27
  49. package/dist/engine/pipeline/build.exec.js.map +1 -1
  50. package/dist/engine/pipeline/ensemble.exec.d.ts +12 -0
  51. package/dist/engine/pipeline/ensemble.exec.js +123 -30
  52. package/dist/engine/pipeline/ensemble.exec.js.map +1 -1
  53. package/dist/engine/pipeline/phase.graph.d.ts +31 -0
  54. package/dist/engine/pipeline/phase.graph.js +102 -0
  55. package/dist/engine/pipeline/phase.graph.js.map +1 -0
  56. package/dist/engine/pipeline/phase.sequence.d.ts +3 -1
  57. package/dist/engine/pipeline/phase.sequence.js +50 -21
  58. package/dist/engine/pipeline/phase.sequence.js.map +1 -1
  59. package/dist/engine/pipeline/pipeline.shared.d.ts +12 -5
  60. package/dist/engine/pipeline/pipeline.shared.js +50 -26
  61. package/dist/engine/pipeline/pipeline.shared.js.map +1 -1
  62. package/dist/engine/pipeline/plan.exec.d.ts +4 -1
  63. package/dist/engine/pipeline/plan.exec.js +15 -12
  64. package/dist/engine/pipeline/plan.exec.js.map +1 -1
  65. package/dist/engine/pipeline/prompt.document.d.ts +28 -0
  66. package/dist/engine/pipeline/prompt.document.js +50 -0
  67. package/dist/engine/pipeline/prompt.document.js.map +1 -0
  68. package/dist/engine/pipeline/refine.exec.js +14 -14
  69. package/dist/engine/pipeline/refine.exec.js.map +1 -1
  70. package/dist/engine/pipeline/research.exec.d.ts +0 -2
  71. package/dist/engine/pipeline/research.exec.js +31 -58
  72. package/dist/engine/pipeline/research.exec.js.map +1 -1
  73. package/dist/engine/pipeline/review.exec.d.ts +1 -1
  74. package/dist/engine/pipeline/review.exec.js +23 -31
  75. package/dist/engine/pipeline/review.exec.js.map +1 -1
  76. package/dist/engine/pipeline/specify.exec.js +15 -24
  77. package/dist/engine/pipeline/specify.exec.js.map +1 -1
  78. package/dist/engine/pipeline/worktree.parallel.d.ts +22 -0
  79. package/dist/engine/pipeline/worktree.parallel.js +122 -0
  80. package/dist/engine/pipeline/worktree.parallel.js.map +1 -0
  81. package/dist/flavours/web-game/core/builder.md +5 -2
  82. package/dist/flavours/web-game/core/designer.md +157 -0
  83. package/dist/git.js +11 -4
  84. package/dist/git.js.map +1 -1
  85. package/dist/plugin/visual-tools/skills/agent-browser/SKILL.md +1 -1
  86. package/dist/plugin/visual-tools/skills/canvas-screenshot/SKILL.md +1 -1
  87. package/dist/stores/budget.js +21 -17
  88. package/dist/stores/budget.js.map +1 -1
  89. package/dist/stores/handoff.d.ts +4 -0
  90. package/dist/stores/handoff.js +28 -1
  91. package/dist/stores/handoff.js.map +1 -1
  92. package/dist/stores/phases.d.ts +8 -0
  93. package/dist/stores/phases.js +23 -2
  94. package/dist/stores/phases.js.map +1 -1
  95. package/dist/stores/settings.d.ts +1 -0
  96. package/dist/stores/settings.js.map +1 -1
  97. package/dist/stores/state.d.ts +6 -1
  98. package/dist/stores/state.js +101 -19
  99. package/dist/stores/state.js.map +1 -1
  100. package/dist/types.d.ts +3 -0
  101. package/dist/ui/logger.d.ts +11 -0
  102. package/dist/ui/logger.js +71 -0
  103. package/dist/ui/logger.js.map +1 -0
  104. package/dist/ui/output.d.ts +1 -0
  105. package/dist/ui/output.js +11 -1
  106. package/dist/ui/output.js.map +1 -1
  107. package/dist/utils/file-lock.d.ts +5 -0
  108. package/dist/utils/file-lock.js +95 -0
  109. package/dist/utils/file-lock.js.map +1 -0
  110. package/package.json +6 -3
package/README.md CHANGED
@@ -5,35 +5,39 @@
5
5
  Build harness for long-horizon software execution using AI agents.
6
6
 
7
7
  Ridgeline decomposes large software ideas into phased builds using a
8
- multi-agent pipeline (shaper, specifier, researcher, refiner, planner, builder,
9
- reviewer) driven by the Claude CLI. It manages state through git checkpoints,
8
+ multi-agent pipeline (shaper, designer, specifier, researcher, refiner, planner,
9
+ builder, reviewer) driven by the Claude CLI. It manages state through git checkpoints,
10
10
  tracks costs, and supports resumable execution when things go wrong.
11
11
 
12
12
  ## How it works
13
13
 
14
14
  1. **Shape** -- describe what you want built. The shaper agent analyzes your
15
15
  codebase and asks clarifying questions to produce a structured shape document.
16
- 2. **Specify** -- an ensemble of three specialist agents (completeness, clarity,
16
+ 2. **Design** (optional) -- the designer agent establishes a visual design system
17
+ (`design.md`) through interactive Q&A. Auto-runs the asset catalog if assets
18
+ exist, and injects catalog context (detected style, palette, resolution) into
19
+ the design conversation. Works at build level or project level.
20
+ 3. **Specify** -- an ensemble of three specialist agents (completeness, clarity,
17
21
  pragmatism) drafts spec proposals, then a synthesizer merges them into
18
22
  `spec.md`, `constraints.md`, and optionally `taste.md`.
19
- 3. **Research** (optional) -- an ensemble of research specialists (academic,
23
+ 4. **Research** (optional) -- an ensemble of research specialists (academic,
20
24
  ecosystem, competitive) investigates the spec using web sources, then a
21
25
  synthesizer merges findings into `research.md`. A gap analysis agenda step
22
26
  runs before specialist dispatch to focus research on spec gaps. Findings
23
27
  accumulate across iterations rather than being overwritten. A quick
24
28
  single-agent mode is also available. See [Research and Refine](docs/research.md).
25
- 4. **Refine** (optional) -- the refiner agent rewrites `spec.md` incorporating
29
+ 5. **Refine** (optional) -- the refiner agent rewrites `spec.md` incorporating
26
30
  research findings and writes `spec.changelog.md` documenting what changed.
27
31
  Additive by default -- adds insights without removing user-authored content.
28
- 5. **Plan** -- an ensemble of three specialist planners (simplicity,
32
+ 6. **Plan** -- an ensemble of three specialist planners (simplicity,
29
33
  thoroughness, velocity) proposes phase decompositions, then a synthesizer
30
34
  merges them into numbered phase files with acceptance criteria.
31
- 6. **Build** -- for each phase the builder agent implements the spec inside your
35
+ 7. **Build** -- for each phase the builder agent implements the spec inside your
32
36
  repo, then creates a git checkpoint.
33
- 7. **Review** -- the reviewer agent (read-only) checks the output against the
37
+ 8. **Review** -- the reviewer agent (read-only) checks the output against the
34
38
  acceptance criteria and returns a structured verdict. On failure, the harness
35
39
  generates a feedback file from the verdict for the builder's next attempt.
36
- 8. **Retry or advance** -- failed phases are retried up to a configurable limit;
40
+ 9. **Retry or advance** -- failed phases are retried up to a configurable limit;
37
41
  passing phases hand off context to the next one.
38
42
 
39
43
  ## Install
@@ -42,6 +46,8 @@ tracks costs, and supports resumable execution when things go wrong.
42
46
  npm install -g ridgeline
43
47
  ```
44
48
 
49
+ **Platform:** macOS and Linux. Windows is not supported.
50
+
45
51
  Ridgeline requires the [Claude CLI](https://docs.anthropic.com/en/docs/claude-code)
46
52
  to be installed and authenticated.
47
53
 
@@ -63,13 +69,17 @@ ridgeline my-feature "Build a REST API for task management"
63
69
 
64
70
  # Or run each stage individually
65
71
  ridgeline shape my-feature "Build a REST API for task management"
72
+ ridgeline design my-feature # optional: establish visual design system
66
73
  ridgeline spec my-feature
67
- ridgeline research my-feature --deep # optional: enrich spec with web research
68
- ridgeline refine my-feature # optional: merge research into spec
74
+ ridgeline research my-feature --deep # optional: enrich spec with web research
75
+ ridgeline refine my-feature # optional: merge research into spec
69
76
  ridgeline plan my-feature
70
77
  ridgeline dry-run my-feature # preview before committing
71
78
  ridgeline build my-feature
72
79
 
80
+ # Catalog media assets (images, audio, video, text)
81
+ ridgeline catalog my-feature --classify --describe
82
+
73
83
  # Resume after a failure (re-run build)
74
84
  ridgeline build my-feature
75
85
 
@@ -85,8 +95,8 @@ ridgeline clean
85
95
  ### `ridgeline [build-name] [input]` (default)
86
96
 
87
97
  Auto-advances the build through the next incomplete pipeline stage
88
- (shape → spec → plan → build; research and refine are opt-in). Accepts all
89
- flags from the individual commands.
98
+ (shape → spec → plan → build; design, research, and refine are opt-in).
99
+ Accepts all flags from the individual commands.
90
100
 
91
101
  ### `ridgeline shape [build-name] [input]`
92
102
 
@@ -100,6 +110,20 @@ path to an existing document or a natural language description.
100
110
  | `--timeout <minutes>` | `10` | Max duration per turn |
101
111
  | `--flavour <name-or-path>` | none | Agent flavour: built-in name or path to custom agents |
102
112
 
113
+ ### `ridgeline design [build-name]`
114
+
115
+ Establishes or updates a visual design system through interactive Q&A. Produces
116
+ `design.md` in the build directory (or project-level if no build name is given).
117
+ If an asset directory exists but no catalog has been built, the catalog is
118
+ auto-run and its summary (detected style, palette, resolution, category
119
+ breakdown) is injected into the designer's context.
120
+
121
+ | Flag | Default | Description |
122
+ |------|---------|-------------|
123
+ | `--model <name>` | `opus` | Model for designer agent |
124
+ | `--timeout <minutes>` | `10` | Max duration per turn |
125
+ | `--flavour <name-or-path>` | none | Agent flavour: built-in name or path to custom agents |
126
+
103
127
  ### `ridgeline spec [build-name]`
104
128
 
105
129
  Runs the specifier ensemble: three specialist agents (completeness, clarity,
@@ -189,7 +213,70 @@ Resets pipeline state to a given stage and deletes downstream artifacts.
189
213
 
190
214
  | Flag | Default | Description |
191
215
  |------|---------|-------------|
192
- | `--to <stage>` | (required) | Stage to rewind to: `shape`, `spec`, `research`, `refine`, or `plan` |
216
+ | `--to <stage>` | (required) | Stage to rewind to: `shape`, `design`, `spec`, `research`, `refine`, or `plan` |
217
+
218
+ ### `ridgeline catalog [build-name]`
219
+
220
+ Indexes media assets into `asset-catalog.json` — a structured metadata file that
221
+ feeds into the design and build phases. Supports images, audio, video, and text
222
+ files. The catalog pipeline runs in three tiers:
223
+
224
+ 1. **Deterministic metadata** (always runs) — scans the asset directory, extracts
225
+ file metadata (size, hash, dimensions for images), detects spritesheets and
226
+ tileable textures, infers category from directory structure and filename
227
+ conventions (e.g., `characters/knight-walk.png` → category "characters",
228
+ subject "knight", state "walk"). Computes project-wide visual identity
229
+ aggregates (detected style, palette, resolution).
230
+ 2. **Classification** (with `--classify`) — assigns categories to uncategorized
231
+ files. Filename heuristics run first (e.g., `bg_*` → backgrounds, `sfx_*` →
232
+ sfx). Files that don't match any pattern fall through to AI classification
233
+ using Claude vision for images or text prompts for other media types.
234
+ 3. **Vision enrichment** (with `--describe`) — uses Claude vision to add semantic
235
+ descriptions, facing direction, pose, style tags, and animation type for image
236
+ assets. Layout and UI assets are auto-described regardless of the flag.
237
+ 4. **Sprite packing** (with `--pack`) — groups image assets by category and packs
238
+ them into 2048×2048 sprite atlases with PixiJS-compatible JSON metadata.
239
+ Backgrounds and layout references are excluded.
240
+
241
+ The catalog is incremental — unchanged files (by content hash) are skipped on
242
+ subsequent runs unless `--force` is set.
243
+
244
+ | Flag | Default | Description |
245
+ |------|---------|-------------|
246
+ | `--asset-dir <path>` | auto | Path to asset directory |
247
+ | `--classify` | off | AI-classify uncategorized files into categories |
248
+ | `--describe` | off | Add vision-based descriptions for all image assets |
249
+ | `--pack` | off | Generate sprite atlases after cataloging |
250
+ | `--batch` | off | Batch multiple images per vision call |
251
+ | `--force` | off | Re-process all assets ignoring content hash |
252
+ | `--model <name>` | `opus` | Model for vision and classification |
253
+ | `--timeout <minutes>` | `5` | Max duration per AI call |
254
+
255
+ Asset directory is resolved in order: `--asset-dir` flag,
256
+ `.ridgeline/builds/<build-name>/assets/`, `.ridgeline/assets/`, or the
257
+ `assetDir` field in `settings.json`.
258
+
259
+ ### `ridgeline retrospective [build-name]`
260
+
261
+ Analyzes a completed build and extracts learnings for future builds. Reads the
262
+ trajectory log, budget, state, and any feedback files, then appends structured
263
+ insights to `.ridgeline/learnings.md`. Future builds automatically pick up these
264
+ learnings if the file exists.
265
+
266
+ | Flag | Default | Description |
267
+ |------|---------|-------------|
268
+ | `--model <name>` | `opus` | Model for retrospective agent |
269
+ | `--timeout <minutes>` | `10` | Max duration |
270
+ | `--flavour <name-or-path>` | none | Agent flavour: built-in name or path to custom agents |
271
+
272
+ ### `ridgeline check`
273
+
274
+ Checks recommended tools and prerequisites for a flavour. Reports which
275
+ external tools are available and which are missing.
276
+
277
+ | Flag | Default | Description |
278
+ |------|---------|-------------|
279
+ | `--flavour <name-or-path>` | from settings | Agent flavour to check |
193
280
 
194
281
  ### `ridgeline clean`
195
282
 
@@ -201,15 +288,19 @@ WIP branches. Use this after inspecting a failed build.
201
288
  ```text
202
289
  .ridgeline/
203
290
  ├── settings.json # Optional project-level config (network allowlist, etc.)
291
+ ├── design.md # Optional project-level visual design system
292
+ ├── learnings.md # Optional accumulated build learnings (from retrospective)
204
293
  ├── worktrees/ # Git worktrees for active builds
205
294
  │ └── <build-name>/ # Isolated working directory per build
206
295
  └── builds/<build-name>/
207
296
  ├── shape.md # Structured project context (from shaper)
297
+ ├── design.md # Optional visual design system (from designer)
208
298
  ├── spec.md # What to build
209
299
  ├── constraints.md # Technical constraints and check commands
210
300
  ├── taste.md # Optional coding style preferences
211
301
  ├── research.md # Optional research findings (from researcher)
212
302
  ├── spec.changelog.md # Optional changelog of spec refinements
303
+ ├── asset-catalog.json # Optional indexed media assets (from catalog)
213
304
  ├── phases/
214
305
  │ ├── 01-scaffold.md
215
306
  │ ├── 01-scaffold.feedback.md # Generated by harness on review failure
@@ -53,12 +53,22 @@ Round 1 — Art Direction:
53
53
  - Art style: pixel art, vector, 3D, hand-drawn, realistic
54
54
  - Color palette: mood, saturation level, palette constraints
55
55
  - Asset dimensions: sprite sizes, texture resolutions, canvas size
56
+ - Shape language: proportions (chunky/slim), corners (rounded/sharp), detail level
57
+ - Rendering: pixel scale, scaling mode (nearest/bilinear), canvas size
56
58
 
57
59
  Round 2 — UI & HUD:
58
60
 
59
61
  - HUD/overlay style: transparency, position, font choices
60
62
  - Menu design: navigation patterns, transition styles
61
63
  - In-game text: dialogue boxes, tooltips, damage numbers
64
+ - Layout regions: where health, score, inventory, and action buttons go
65
+ - Mood: overall atmosphere and tone in a brief phrase
66
+
67
+ Round 3 — Asset Integration (when asset catalog data is in context):
68
+
69
+ - Asset manifest review: confirm discovered assets match creative intent
70
+ - Background treatment: mood, parallax, scroll behavior for each background
71
+ - Asset loading strategy: preload vs lazy, atlas format, base path
62
72
 
63
73
  **For print-layout projects:**
64
74
 
@@ -129,3 +139,11 @@ The format is flexible — brand guidelines, informal notes, formal style guides
129
139
  **Respect existing design.md.** If one exists, read it as starting context. Offer to refine or extend, don't start from scratch unless asked.
130
140
 
131
141
  **Stay in design territory.** Don't ask about code architecture, error handling, or implementation details. Those belong to the shaper and specifier.
142
+
143
+ **When asset catalog data is present in context:**
144
+
145
+ - Propose palette, style, resolution, and scaling defaults derived from the catalog's visual identity analysis. Include these as `suggestedAnswer` values.
146
+ - Present the asset manifest summary for user confirmation.
147
+ - Use layout region data (if available) to propose HUD/menu arrangements.
148
+ - Flag any catalog warnings about palette mismatches for user review.
149
+ - Cover asset loading strategy (preload/lazy, format, base path) in your questions.
@@ -65,6 +65,38 @@ Every phase file must follow this structure exactly:
65
65
  <Relevant sections of spec.md for this phase, quoted or summarized.>
66
66
  ```
67
67
 
68
+ ## Phase Dependencies (Parallel Execution)
69
+
70
+ Phases can declare dependencies to enable parallel execution. When a phase depends only on a subset of prior phases (not the immediately preceding one), add YAML frontmatter:
71
+
72
+ ```markdown
73
+ ---
74
+ depends_on: [01-scaffold]
75
+ ---
76
+ # Phase 3: API Endpoints
77
+ ...
78
+ ```
79
+
80
+ **Rules for dependencies:**
81
+
82
+ - Phases without frontmatter automatically depend on the immediately preceding phase (sequential execution).
83
+ - A phase can only depend on phases with a lower index number.
84
+ - If a phase reads or modifies files created by another phase, it must depend on that phase.
85
+ - Phase 01 never has dependencies (it is the root).
86
+ - Use dependencies to enable parallelism when phases work on independent parts of the codebase.
87
+ - When in doubt, omit the frontmatter. False parallelism is worse than false sequentiality.
88
+
89
+ **Example: fan-out pattern**
90
+
91
+ ```text
92
+ 01-scaffold (no deps — root)
93
+ 02-api depends_on: [01-scaffold]
94
+ 03-ui depends_on: [01-scaffold]
95
+ 04-integration depends_on: [02-api, 03-ui]
96
+ ```
97
+
98
+ Phases 02 and 03 run in parallel after 01 completes. Phase 04 waits for both.
99
+
68
100
  ## Rules
69
101
 
70
102
  **No implementation details.** Do not specify creation order, internal structure, sub-agent assignments, implementation patterns, or approach. The builder decides all of this. You describe the destination, not the route.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: retrospective
3
+ description: Analyzes a completed build to extract learnings, patterns, and recommendations for future builds
4
+ model: opus
5
+ ---
6
+
7
+ You are a build retrospective analyst. After a build completes, you analyze the trajectory, budget, feedback files, and final state to extract actionable learnings.
8
+
9
+ ## Your inputs
10
+
11
+ These are injected into your context:
12
+
13
+ 1. **trajectory.jsonl** — chronological event log of the entire build (plan, build, review, retry events with durations and costs)
14
+ 2. **budget.json** — per-phase, per-role cost breakdown
15
+ 3. **Feedback files** — reviewer verdicts and feedback from any retried phases
16
+ 4. **state.json** — final build state with phase statuses, durations, and retry counts
17
+
18
+ ## Your process
19
+
20
+ ### 1. Analyze the build trajectory
21
+
22
+ - Which phases completed cleanly on the first attempt?
23
+ - Which phases required retries? What were the reviewer's objections?
24
+ - Where was the most time and money spent?
25
+ - Were there any patterns in failures (e.g., the same type of issue recurring)?
26
+
27
+ ### 2. Extract learnings
28
+
29
+ Produce a structured retrospective in the following format. Be specific — name files, patterns, and concrete observations. Avoid generic advice.
30
+
31
+ ```markdown
32
+ ## Build: {build-name} ({date})
33
+
34
+ ### What Worked
35
+ - Specific things that went well (clean passes, efficient phases)
36
+
37
+ ### What Didn't
38
+ - Specific failures, retries, and their root causes
39
+
40
+ ### Patterns to Repeat
41
+ - Concrete patterns worth carrying forward (spec structures, constraint phrasings, phase granularity choices)
42
+
43
+ ### Patterns to Avoid
44
+ - Anti-patterns observed (overly broad phases, missing constraints, spec ambiguities)
45
+
46
+ ### Cost Analysis
47
+ - Total cost and duration
48
+ - Most expensive phases and why
49
+ - Efficiency observations
50
+
51
+ ### Recommendations for Next Build
52
+ - Specific, actionable suggestions for improving spec, constraints, or phase structure
53
+ ```
54
+
55
+ ### 3. Write the output
56
+
57
+ Append your retrospective to the learnings file. Do NOT overwrite previous entries — each build's learnings accumulate.
58
+
59
+ ## Rules
60
+
61
+ - Be concrete and specific, not generic. "Phase 03 failed because the spec didn't mention auth middleware" is useful. "Consider being more specific in specs" is not.
62
+ - Focus on what the build artifacts reveal, not hypotheticals.
63
+ - Keep each section to 3-5 bullet points. Quality over quantity.
64
+ - If the build completed cleanly with no retries, say so — a clean build is still worth noting.
@@ -0,0 +1,21 @@
1
+ import { AssetCatalog, CatalogOptions } from "./types";
2
+ export type CatalogResult = {
3
+ catalog: AssetCatalog;
4
+ stats: {
5
+ total: number;
6
+ added: number;
7
+ updated: number;
8
+ unchanged: number;
9
+ pruned: number;
10
+ classified: number;
11
+ };
12
+ /** Files in auto-describe categories that need vision enrichment. */
13
+ needsVisionDescribe: string[];
14
+ };
15
+ type BuildCatalogOpts = Pick<CatalogOptions, "isForce" | "isClassify" | "model" | "timeout">;
16
+ /**
17
+ * Build or update the asset catalog.
18
+ * Handles all media types. Vision enrichment and sprite packing are handled separately.
19
+ */
20
+ export declare const buildCatalog: (assetDir: string, buildDir: string, opts: BuildCatalogOpts) => Promise<CatalogResult>;
21
+ export {};
@@ -0,0 +1,305 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildCatalog = void 0;
37
+ const fs = __importStar(require("node:fs"));
38
+ const path = __importStar(require("node:path"));
39
+ const parse_conventions_1 = require("./parse-conventions");
40
+ const extract_metadata_1 = require("./extract-metadata");
41
+ const classify_1 = require("./classify");
42
+ /** Map file extensions to media types. */
43
+ const MEDIA_EXTENSIONS = {
44
+ // images
45
+ ".png": "image", ".jpg": "image", ".jpeg": "image",
46
+ ".gif": "image", ".webp": "image", ".svg": "image", ".avif": "image",
47
+ // audio
48
+ ".mp3": "audio", ".wav": "audio", ".ogg": "audio",
49
+ ".flac": "audio", ".aac": "audio", ".m4a": "audio",
50
+ // video
51
+ ".mp4": "video", ".webm": "video", ".mov": "video", ".avi": "video",
52
+ // text
53
+ ".txt": "text", ".json": "text", ".csv": "text",
54
+ ".md": "text", ".yaml": "text", ".yml": "text",
55
+ };
56
+ /** Detect media type from file extension. */
57
+ const detectMediaType = (filePath) => MEDIA_EXTENSIONS[path.extname(filePath).toLowerCase()] ?? null;
58
+ /** Recursively walk a directory tree and return all asset file paths (relative to root). */
59
+ const walkAssets = (dir, root) => {
60
+ const base = root ?? dir;
61
+ const results = [];
62
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
63
+ const fullPath = path.join(dir, entry.name);
64
+ if (entry.isDirectory()) {
65
+ results.push(...walkAssets(fullPath, base));
66
+ }
67
+ else if (detectMediaType(entry.name) !== null) {
68
+ results.push(path.relative(base, fullPath));
69
+ }
70
+ }
71
+ return results;
72
+ };
73
+ /** Load existing catalog from disk, if present. */
74
+ const loadExistingCatalog = (catalogPath) => {
75
+ if (!fs.existsSync(catalogPath))
76
+ return null;
77
+ try {
78
+ return JSON.parse(fs.readFileSync(catalogPath, "utf-8"));
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ };
84
+ /** Build a lookup map from an existing catalog for incremental updates. */
85
+ const buildHashIndex = (catalog) => {
86
+ const index = new Map();
87
+ if (!catalog)
88
+ return index;
89
+ for (const entry of catalog.assets) {
90
+ index.set(entry.file, entry);
91
+ }
92
+ return index;
93
+ };
94
+ /** Detect the most common resolution among square or nearly-square image assets. */
95
+ const detectResolution = (assets) => {
96
+ const sizes = new Map();
97
+ for (const a of assets) {
98
+ if (a.mediaType !== "image")
99
+ continue;
100
+ if (a.isSpritesheet && a.frameSize) {
101
+ const key = `${a.frameSize.w}x${a.frameSize.h}`;
102
+ sizes.set(key, (sizes.get(key) ?? 0) + 1);
103
+ }
104
+ else if (a.width && a.height && a.width === a.height && a.width <= 256) {
105
+ const key = `${a.width}x${a.height}`;
106
+ sizes.set(key, (sizes.get(key) ?? 0) + 1);
107
+ }
108
+ }
109
+ if (sizes.size === 0)
110
+ return null;
111
+ return [...sizes.entries()].sort((a, b) => b[1] - a[1])[0][0];
112
+ };
113
+ /** Build aggregate palette from all image assets (top 8 most frequent colours). */
114
+ const detectPalette = (assets) => {
115
+ const freq = new Map();
116
+ for (const a of assets) {
117
+ if (!a.palette)
118
+ continue;
119
+ for (const c of a.palette) {
120
+ freq.set(c, (freq.get(c) ?? 0) + 1);
121
+ }
122
+ }
123
+ return [...freq.entries()]
124
+ .sort((a, b) => b[1] - a[1])
125
+ .slice(0, 8)
126
+ .map(([colour]) => colour);
127
+ };
128
+ /** Detect visual style from image asset properties. */
129
+ const detectStyle = (assets) => {
130
+ const imageAssets = assets.filter((a) => a.mediaType === "image");
131
+ if (imageAssets.length === 0)
132
+ return null;
133
+ const smallAssets = imageAssets.filter((a) => {
134
+ const size = a.isSpritesheet && a.frameSize
135
+ ? Math.max(a.frameSize.w, a.frameSize.h)
136
+ : Math.max(a.width ?? 0, a.height ?? 0);
137
+ return size <= 128;
138
+ });
139
+ if (smallAssets.length > imageAssets.length * 0.6)
140
+ return "pixel-art";
141
+ const svgCount = imageAssets.filter((a) => a.format === "svg").length;
142
+ if (svgCount > imageAssets.length * 0.5)
143
+ return "vector";
144
+ return null;
145
+ };
146
+ /** Derive aggregate visual identity from all cataloged assets. */
147
+ const deriveVisualIdentity = (assets) => {
148
+ const style = detectStyle(assets);
149
+ return {
150
+ detectedStyle: style,
151
+ detectedPalette: detectPalette(assets),
152
+ detectedResolution: detectResolution(assets),
153
+ detectedScaling: style === "pixel-art" ? "nearest" : null,
154
+ };
155
+ };
156
+ /** Compare detected palette against design.md palette and produce warnings. */
157
+ const checkPaletteMismatches = (assets, buildDir, ridgelineDir) => {
158
+ const warnings = [];
159
+ const designPaths = [
160
+ path.join(buildDir, "design.md"),
161
+ path.join(ridgelineDir, "design.md"),
162
+ ];
163
+ let designContent = null;
164
+ for (const p of designPaths) {
165
+ if (fs.existsSync(p)) {
166
+ designContent = fs.readFileSync(p, "utf-8");
167
+ break;
168
+ }
169
+ }
170
+ if (!designContent)
171
+ return warnings;
172
+ const hexPattern = /#[0-9a-fA-F]{6}/g;
173
+ const designColours = new Set([...designContent.matchAll(hexPattern)].map((m) => m[0].toLowerCase()));
174
+ if (designColours.size === 0)
175
+ return warnings;
176
+ for (const asset of assets) {
177
+ if (!asset.palette)
178
+ continue;
179
+ const offPalette = asset.palette.filter((c) => !designColours.has(c.toLowerCase()));
180
+ if (offPalette.length > 0) {
181
+ warnings.push(`${asset.file} uses colours (${offPalette.join(", ")}) not found in design.md palette. This may be intentional.`);
182
+ }
183
+ }
184
+ return warnings;
185
+ };
186
+ /**
187
+ * Build or update the asset catalog.
188
+ * Handles all media types. Vision enrichment and sprite packing are handled separately.
189
+ */
190
+ const buildCatalog = async (assetDir, buildDir, opts) => {
191
+ const ridgelineDir = path.join(process.cwd(), ".ridgeline");
192
+ const catalogPath = path.join(buildDir, "asset-catalog.json");
193
+ const existing = loadExistingCatalog(catalogPath);
194
+ const hashIndex = buildHashIndex(existing);
195
+ const assetFiles = walkAssets(assetDir);
196
+ const existingFiles = new Set(hashIndex.keys());
197
+ const assets = [];
198
+ const needsVisionDescribe = [];
199
+ let added = 0;
200
+ let updated = 0;
201
+ let unchanged = 0;
202
+ let classified = 0;
203
+ const timeoutMs = opts.timeout * 60 * 1000;
204
+ const totalFiles = assetFiles.length;
205
+ let processedCount = 0;
206
+ for (const relPath of assetFiles) {
207
+ const absPath = path.join(assetDir, relPath);
208
+ const hash = (0, extract_metadata_1.computeContentHash)(absPath);
209
+ const prev = hashIndex.get(relPath);
210
+ existingFiles.delete(relPath);
211
+ // Skip unchanged files (unless --force)
212
+ if (!opts.isForce && prev && prev.hash === hash) {
213
+ assets.push(prev);
214
+ unchanged++;
215
+ // Still track if auto-describe category needs vision
216
+ if (prev.mediaType === "image") {
217
+ const conv = (0, parse_conventions_1.parseConventions)(relPath);
218
+ if (parse_conventions_1.AUTO_DESCRIBE_CATEGORIES.has(conv.category) && !prev.description) {
219
+ needsVisionDescribe.push(relPath);
220
+ }
221
+ }
222
+ continue;
223
+ }
224
+ processedCount++;
225
+ const mediaType = detectMediaType(relPath);
226
+ const conventions = (0, parse_conventions_1.parseConventions)(relPath);
227
+ const basic = (0, extract_metadata_1.extractBasicMetadata)(absPath);
228
+ // Build the entry — image-specific metadata only for images
229
+ const entry = {
230
+ file: relPath,
231
+ hash,
232
+ mediaType,
233
+ category: conventions.category,
234
+ name: conventions.name,
235
+ subject: conventions.subject,
236
+ state: conventions.state,
237
+ fileSizeBytes: basic.fileSizeBytes,
238
+ extension: basic.extension,
239
+ };
240
+ if (mediaType === "image") {
241
+ const meta = await (0, extract_metadata_1.extractImageMetadata)(absPath);
242
+ const palette = await (0, extract_metadata_1.extractPalette)(absPath);
243
+ const spritesheet = (0, extract_metadata_1.detectSpritesheet)(meta.width, meta.height);
244
+ const isTileable = await (0, extract_metadata_1.detectTileable)(absPath, meta.width, meta.height);
245
+ const defaults = (0, parse_conventions_1.inferDefaults)(conventions.category);
246
+ entry.width = meta.width;
247
+ entry.height = meta.height;
248
+ entry.format = meta.format;
249
+ entry.hasAlpha = meta.hasAlpha;
250
+ entry.channels = meta.channels;
251
+ entry.dominantColour = palette.dominantColour;
252
+ entry.palette = palette.palette;
253
+ entry.isSpritesheet = spritesheet.isSpritesheet;
254
+ entry.frameCount = spritesheet.frameCount;
255
+ entry.frameSize = spritesheet.frameSize;
256
+ entry.frameDirection = spritesheet.frameDirection;
257
+ entry.suggestedAnchor = defaults.anchor;
258
+ entry.suggestedZLayer = defaults.zLayer;
259
+ entry.isTileable = isTileable;
260
+ }
261
+ // Classify uncategorized files when --classify is set
262
+ if (conventions.category === "uncategorized" && opts.isClassify) {
263
+ process.stderr.write(`\x1b[90m Classifying ${relPath} (${processedCount}/${totalFiles})...\x1b[0m\n`);
264
+ const result = (0, classify_1.classifyByHeuristics)(relPath, basic.extension, mediaType)
265
+ ?? (0, classify_1.classifyWithAI)(absPath, relPath, basic.extension, mediaType, opts.model, timeoutMs);
266
+ entry.category = result.category;
267
+ entry.isClassified = true;
268
+ entry.classificationConfidence = result.confidence;
269
+ classified++;
270
+ // Update anchor/zLayer defaults based on new category
271
+ if (mediaType === "image") {
272
+ const defaults = (0, parse_conventions_1.inferDefaults)(entry.category);
273
+ entry.suggestedAnchor = defaults.anchor;
274
+ entry.suggestedZLayer = defaults.zLayer;
275
+ }
276
+ }
277
+ assets.push(entry);
278
+ if (mediaType === "image" && parse_conventions_1.AUTO_DESCRIBE_CATEGORIES.has(entry.category)) {
279
+ needsVisionDescribe.push(relPath);
280
+ }
281
+ if (prev) {
282
+ updated++;
283
+ }
284
+ else {
285
+ added++;
286
+ }
287
+ }
288
+ // Remaining entries in existingFiles are pruned (files removed from disk)
289
+ const pruned = existingFiles.size;
290
+ const catalog = {
291
+ generatedAt: new Date().toISOString(),
292
+ assetDir,
293
+ isDescribed: existing?.isDescribed ?? false,
294
+ visualIdentity: deriveVisualIdentity(assets),
295
+ warnings: checkPaletteMismatches(assets, buildDir, ridgelineDir),
296
+ assets,
297
+ };
298
+ return {
299
+ catalog,
300
+ stats: { total: assets.length, added, updated, unchanged, pruned, classified },
301
+ needsVisionDescribe,
302
+ };
303
+ };
304
+ exports.buildCatalog = buildCatalog;
305
+ //# sourceMappingURL=build-catalog.js.map