@jenga-ai/agent 3.6.0 → 4.1.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 (86) hide show
  1. package/README.md +32 -1
  2. package/lib/generate-skill-allow-list.js +42 -5
  3. package/lib/skill-allow-list.json +1 -1
  4. package/package.json +1 -2
  5. package/project/app/api/lib/resolve-project-root.js +1 -1
  6. package/project/app/api/scripts/capture-snapshot.js +12 -7
  7. package/project/app/package.json +4 -0
  8. package/project/app/ui/dist/assets/index-BADc5mmH.css +1 -0
  9. package/project/app/ui/dist/assets/index-DX2pfTAW.js +104 -0
  10. package/project/app/ui/dist/index.html +2 -2
  11. package/scripts/acquire-concurrency-slot.sh +34 -4
  12. package/scripts/apply-j-prefix.sh +1 -1
  13. package/scripts/delete-bare-skill-dirs.sh +1 -2
  14. package/scripts/idea_manager.sh +16 -1
  15. package/scripts/release-concurrency-slot.sh +34 -4
  16. package/scripts/render-ranked-list.sh +270 -0
  17. package/scripts/repoint-dead-bare-path-prose.py +81 -0
  18. package/scripts/rewrite-stale-skill-preambles.py +188 -0
  19. package/scripts/strip-polyfill-frontmatter.py +166 -0
  20. package/scripts/todo_manager.sh +16 -1
  21. package/scripts/validate-typed-object.sh +750 -0
  22. package/scripts/verify-postinstall-reconcile.sh +33 -7
  23. package/skills/index/scripts/board_index.py +29 -1
  24. package/skills/j-brainstorm/SKILL.md +3 -4
  25. package/skills/j-btw/SKILL.md +3 -4
  26. package/skills/j-clearify/SKILL.md +3 -4
  27. package/skills/j-close-story/SKILL.md +11 -12
  28. package/skills/j-close-story/scripts/check-story-closeable.sh +11 -4
  29. package/skills/j-commit/SKILL.md +3 -4
  30. package/skills/j-continue/SKILL.md +5 -6
  31. package/skills/j-deep-dive/SKILL.md +3 -4
  32. package/skills/j-distribute/SKILL.md +3 -4
  33. package/skills/j-do/SKILL.md +13 -15
  34. package/skills/j-doc/SKILL.md +3 -4
  35. package/skills/j-doc/scripts/resolve_last_update.py +27 -1
  36. package/skills/j-doc-sync/SKILL.md +3 -4
  37. package/skills/j-dooo/SKILL.md +6 -15
  38. package/skills/j-error/SKILL.md +3 -4
  39. package/skills/j-evaluate/SKILL.md +3 -4
  40. package/skills/j-examplify/SKILL.md +3 -4
  41. package/skills/j-help/SKILL.md +3 -4
  42. package/skills/j-idea/SKILL.md +3 -4
  43. package/skills/j-improve/SKILL.md +3 -4
  44. package/skills/j-init/SKILL.md +20 -11
  45. package/skills/j-init/scripts/apply-scaffold-visibility.sh +8 -6
  46. package/skills/j-jbp/SKILL.md +3 -4
  47. package/skills/j-lgtm/SKILL.md +3 -4
  48. package/skills/j-pi-plan/SKILL.md +3 -4
  49. package/skills/j-proceed/SKILL.md +3 -4
  50. package/skills/j-publish/SKILL.md +3 -4
  51. package/skills/j-publish/scripts/run_gates.sh +1 -1
  52. package/skills/j-reconcile/SKILL.md +38 -5
  53. package/skills/j-reconcile/assets/report_format.md +11 -0
  54. package/skills/j-reconcile/scripts/detect-unlinked-code.sh +2 -2
  55. package/skills/j-reconcile-origin/SKILL.md +3 -4
  56. package/skills/j-redo/SKILL.md +3 -4
  57. package/skills/j-skillify/SKILL.md +3 -4
  58. package/skills/j-spinoff/SKILL.md +3 -4
  59. package/skills/j-status/SKILL.md +4 -5
  60. package/skills/j-todo/SKILL.md +42 -5
  61. package/skills/j-todo/scripts/add_trivial_task.sh +12 -1
  62. package/skills/j-todo/scripts/argument-is-not-ranked-list.sh +92 -0
  63. package/skills/j-todo/scripts/argument-is-ranked-list.sh +78 -0
  64. package/skills/j-uncharted/SKILL.md +251 -12
  65. package/skills/j-uncharted/scripts/detect-dependencies.sh +79 -21
  66. package/skills/j-uncharted/scripts/diff-since-baseline.sh +600 -0
  67. package/skills/j-uncharted/scripts/find-scan-baseline.sh +545 -0
  68. package/skills/j-uncharted/scripts/run-engine.sh +36 -2
  69. package/skills/j-uncharted/scripts/write-scan-record.sh +361 -0
  70. package/skills/j-wtf/SKILL.md +3 -4
  71. package/skills/jenga/SKILL.md +106 -10
  72. package/skills/jenga/playbooks/schema.json +4 -4
  73. package/skills/jenga/scripts/enrich-nl-prompt.sh +225 -0
  74. package/skills/jenga/scripts/load-nl-catalog.js +6 -3
  75. package/skills/jenga/scripts/load-playbooks.sh +289 -4
  76. package/skills/jenga/scripts/match-playbook.sh +6 -5
  77. package/skills/jenga/scripts/run-playbook-step.sh +270 -3
  78. package/templates/permission-levels/level-1-locked.json +1 -1
  79. package/templates/permission-levels/level-2-guarded.json +1 -1
  80. package/templates/permission-levels/level-3-standard.json +1 -1
  81. package/templates/permission-levels/level-4-elevated.json +1 -1
  82. package/templates/permission-levels/level-5-unrestricted.json +1 -1
  83. package/templates/playbook-types.json +6 -0
  84. package/project/app/ui/dist/assets/index-BVR_7Owg.css +0 -1
  85. package/project/app/ui/dist/assets/index-CtU2xLQm.js +0 -104
  86. package/scripts/audit-twin-divergence.sh +0 -693
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  # Jenga AI
2
- ### No agent merges its own code.
2
+ ### The AI framework for serious projects.
3
3
 
4
4
  Jenga AI is an agentic software engineering framework for AI-assisted development: it splits work across three role-bounded agents — a Scrum Master that plans, a Developer that implements in an isolated git worktree, and a Tester that runs your test suite and owns the board status. Work survives session boundaries as Epics, Stories, and Tasks on a Markdown board your agent reads at the start of every session.
5
5
 
@@ -29,6 +29,37 @@ You've had this happen: Claude wrote the feature, said it worked, and the sessio
29
29
 
30
30
  Jenga AI solves each of these with structure: persistent engineering context maintained via board state, strict agent roles, typed inter-agent contracts, and session-end hooks that carry that context between sessions.
31
31
 
32
+ ### Documentation
33
+
34
+ Start here, then go deeper as you need it:
35
+
36
+ | Guide | What it covers |
37
+ |---|---|
38
+ | [Intro Guide](project/.wiki/intro-guide.md) | Start here. The philosophy, how to think about the system, and a first-15-minutes walkthrough for a new project or an existing codebase. Also on the [docs site](https://samwelmunga.github.io/jenga-npm/getting-started.html). |
39
+
40
+ **Core concepts** — the three pillars, in the order they build on each other:
41
+
42
+ | Concept | What it covers |
43
+ |---|---|
44
+ | [Role Separation](project/.wiki/concepts/role-separation.md) | Why Jenga AI uses three distinct agents instead of one. |
45
+ | [Board Hierarchy](project/.wiki/concepts/board-hierarchy.md) | Why work is structured as Epics → Stories → Tasks, and how to use that hierarchy well. |
46
+ | [Session Continuity](project/.wiki/concepts/session-continuity.md) | Why AI sessions losing context is the core problem — and how Jenga AI solves it structurally. |
47
+
48
+ **How-to guides** — day-to-day working patterns:
49
+
50
+ | Guide | What it covers |
51
+ |---|---|
52
+ | [Your First Feature](project/.wiki/concepts/first-feature.md) | Building a feature end-to-end, from idea to verified, committed code. |
53
+ | [Capturing Mid-Flow Ideas](project/.wiki/concepts/mid-flow-capture.md) | Handling a new thought, tangent, or feature idea without derailing your current work. |
54
+ | [Working Across Sessions](project/.wiki/concepts/multi-session-work.md) | Picking up a project after a break — without losing momentum or context. |
55
+ | [Parallel Tasks](project/.wiki/concepts/parallel-tasks.md) | Running multiple tasks simultaneously with `j.dooo`, or automating board-wide parallel execution with `j.jenga`. |
56
+
57
+ **Full reference:**
58
+
59
+ | Reference | What it covers |
60
+ |---|---|
61
+ | [Documentation Reference](project/.wiki/documentation.md) | Every skill, every agent, MCP tools, hooks, and the inter-agent communication contract. Also on the [docs site](https://samwelmunga.github.io/jenga-npm/reference.html). |
62
+
32
63
  ---
33
64
 
34
65
  ## How Jenga AI's Agentic Workflow Works
@@ -188,9 +188,21 @@ export function readSkillAllowList(projectRoot, packageRoot = DEFAULT_PACKAGE_RO
188
188
  * Generate the canonical skill allow-list artifact at `outputPath`.
189
189
  *
190
190
  * The `skills` array is deterministic — sorted and de-duplicated, so repeat runs with no
191
- * `skills/` change produce a byte-identical array. `generated_at` is NOT deterministic (it is a
192
- * fresh timestamp on every run) — that is expected and intentional, not a bug: only the `skills`
193
- * array itself is contracted to be stable.
191
+ * `skills/` change produce a byte-identical array.
192
+ *
193
+ * `generated_at` stamps when the allow-list CONTENT was last generated, not when this function
194
+ * last ran: if `skill_count` and `skills` both match what is already on disk at `outputPath`, the
195
+ * existing timestamp is preserved and no write happens at all (`written: false`). Repeat runs over
196
+ * an unchanged `skills/` therefore leave the artifact byte-identical.
197
+ *
198
+ * This is narrower than the original contract, which stamped a fresh timestamp unconditionally and
199
+ * documented that as intentional. It was changed by E42_S07_T01's follow-up because an artifact
200
+ * that changes on every run can never be mirrored to a fixed point: `j.self-sync` copies
201
+ * lib/skill-allow-list.json into .claude/ and .agents/ and then regenerates it, so both mirrors sat
202
+ * one timestamp behind forever and every run reported `~2 overwritten` — contradicting the skill's
203
+ * own documented "+0 ~0 -0 on a second consecutive run" idempotency guarantee. Nothing asserted the
204
+ * every-run churn (tests/helpers/allow-list-scan.mjs compares the `skills` array and explicitly
205
+ * ignores `generated_at`), so no contract anyone depended on is broken.
194
206
  *
195
207
  * @param {string} skillsDir - directory to scan (default: this repo/package's own skills/)
196
208
  * @param {string} outputPath - where to write the JSON artifact (default: lib/skill-allow-list.json)
@@ -199,8 +211,28 @@ export function readSkillAllowList(projectRoot, packageRoot = DEFAULT_PACKAGE_RO
199
211
  export function generateSkillAllowList(skillsDir = DEFAULT_SKILLS_DIR, outputPath = DEFAULT_OUTPUT_PATH) {
200
212
  const skills = getSkillAllowListIdentifiers(skillsDir);
201
213
 
214
+ // Reuse the existing timestamp when the derived content is unchanged, so an unchanged
215
+ // skills/ tree leaves the artifact byte-identical. A malformed or unreadable existing
216
+ // artifact is treated as "no previous content" and simply regenerated.
217
+ let generatedAt = null;
218
+ if (existsSync(outputPath)) {
219
+ try {
220
+ const previous = JSON.parse(readFileSync(outputPath, "utf8"));
221
+ const unchanged =
222
+ previous.skill_count === skills.length &&
223
+ Array.isArray(previous.skills) &&
224
+ previous.skills.length === skills.length &&
225
+ previous.skills.every((s, i) => s === skills[i]);
226
+ if (unchanged && typeof previous.generated_at === "string") {
227
+ generatedAt = previous.generated_at;
228
+ }
229
+ } catch {
230
+ generatedAt = null;
231
+ }
232
+ }
233
+
202
234
  const artifact = {
203
- generated_at: new Date().toISOString(),
235
+ generated_at: generatedAt ?? new Date().toISOString(),
204
236
  skill_count: skills.length,
205
237
  skills,
206
238
  };
@@ -208,7 +240,12 @@ export function generateSkillAllowList(skillsDir = DEFAULT_SKILLS_DIR, outputPat
208
240
  const outDir = dirname(outputPath);
209
241
  if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
210
242
 
211
- writeFileSync(outputPath, JSON.stringify(artifact, null, 2) + "\n", "utf8");
243
+ const next = JSON.stringify(artifact, null, 2) + "\n";
244
+ if (generatedAt !== null && existsSync(outputPath) && readFileSync(outputPath, "utf8") === next) {
245
+ return { written: false, path: outputPath, skill_count: skills.length };
246
+ }
247
+
248
+ writeFileSync(outputPath, next, "utf8");
212
249
 
213
250
  return { written: true, path: outputPath, skill_count: skills.length };
214
251
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "generated_at": "2026-09-20T06:12:06.824Z",
2
+ "generated_at": "2026-09-28T21:17:08.256Z",
3
3
  "skill_count": 41,
4
4
  "skills": [
5
5
  "brainstorm",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jenga-ai/agent",
3
- "version": "3.6.0",
3
+ "version": "4.1.0",
4
4
  "description": "An agentic development workflow for Claude Code, Copilot, and Codex — with a persistent Epic/Story/Task board, an isolated git worktree per task, and a separate tester agent that runs your test suite before anything is marked done.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -22,7 +22,6 @@
22
22
  "generate:legacy-paths": "node scripts/generate-legacy-shipped-paths.js",
23
23
  "graph:populate": "node scripts/populate-knowledge-graph.js",
24
24
  "test": "bats tests/*.bats",
25
- "gate:twin-parity": "bash scripts/audit-twin-divergence.sh . --min-pairs 40",
26
25
  "validate:npm-metadata": "bash scripts/validate_npm_metadata.sh",
27
26
  "ui:dev": "npm run ui:dev --prefix project/app --",
28
27
  "ui:build": "npm run ui:build --prefix project/app --",
@@ -4,7 +4,7 @@
4
4
  * Resolves the invoking (consumer) project's root directory for the dashboard API, so that
5
5
  * `project/app/api/parsers/*.js` never has to compute its data root via a fixed
6
6
  * `path.resolve(__dirname, '../../../...')` climb — the same defect pattern `E46_S01` already fixed
7
- * for `/init` (see `skills/init/scripts/init.sh`'s `PKG_ROOT` idiom and
7
+ * for `/init` (see `skills/j-init/scripts/init.sh`'s `PKG_ROOT` idiom and
8
8
  * `lib/generate-agent-context.js`'s `realpathSync` symlink-safe comparison). A `__dirname` climb only
9
9
  * ever resolves correctly when this module runs from this monorepo's own checkout; once mirrored into
10
10
  * a real consumer's `node_modules/@jenga-ai/agent/`, climbing a fixed number of levels lands inside or
@@ -4,8 +4,9 @@
4
4
  *
5
5
  * E47_S04_T02 — capture step for `j.dashboard --snapshot`.
6
6
  *
7
- * Calls the dashboard API's `/v1/board`, `/v1/history`, `/v1/architecture`, and `/v1/health` routes
8
- * exactly once each and writes their combined JSON to a single artifact, consumable by the
7
+ * Calls the dashboard API's `/v1/board`, `/v1/history`, `/v1/architecture`, `/v1/health`,
8
+ * `/v1/documentation`, and `/v1/rapports` routes exactly once each and writes their combined JSON
9
+ * to a single artifact, consumable by the
9
10
  * bundling/inlining step in `E47_S04_T03` (implemented) and, for `/v1/health` specifically, by
10
11
  * `E47_S06_T02`'s snapshot `<title>` rewrite in `../../ui/scripts/build-snapshot-html.cjs`, which
11
12
  * needs the captured project name to bake into the exported HTML's title at build time.
@@ -44,10 +45,12 @@
44
45
  * "captured_at": "<ISO 8601 UTC timestamp>",
45
46
  * "project_root": "<absolute, realpath'd project root this snapshot was captured against>",
46
47
  * "routes": {
47
- * "board": <full API envelope from GET /v1/board, i.e. { data, meta, error }>,
48
- * "history": <full API envelope from GET /v1/history, i.e. { data, meta, error }>,
49
- * "architecture": <full API envelope from GET /v1/architecture, i.e. { data, meta, error }>,
50
- * "health": <full API envelope from GET /v1/health, i.e. { data, meta, error }>
48
+ * "board": <full API envelope from GET /v1/board, i.e. { data, meta, error }>,
49
+ * "history": <full API envelope from GET /v1/history, i.e. { data, meta, error }>,
50
+ * "architecture": <full API envelope from GET /v1/architecture, i.e. { data, meta, error }>,
51
+ * "health": <full API envelope from GET /v1/health, i.e. { data, meta, error }>,
52
+ * "documentation": <full API envelope from GET /v1/documentation, i.e. { data, meta, error }>,
53
+ * "rapports": <full API envelope from GET /v1/rapports, i.e. { data, meta, error }>
51
54
  * }
52
55
  * }
53
56
  * Each `routes.<name>` value is the *full* envelope exactly as the route returned it (see
@@ -92,12 +95,14 @@ const path = require('path');
92
95
  const http = require('http');
93
96
  const fs = require('fs');
94
97
 
95
- const ROUTES = ['/v1/board', '/v1/history', '/v1/architecture', '/v1/health'];
98
+ const ROUTES = ['/v1/board', '/v1/history', '/v1/architecture', '/v1/health', '/v1/documentation', '/v1/rapports'];
96
99
  const ROUTE_KEYS = {
97
100
  '/v1/board': 'board',
98
101
  '/v1/history': 'history',
99
102
  '/v1/architecture': 'architecture',
100
103
  '/v1/health': 'health',
104
+ '/v1/documentation': 'documentation',
105
+ '/v1/rapports': 'rapports',
101
106
  };
102
107
 
103
108
  function printUsage() {
@@ -17,5 +17,9 @@
17
17
  "ui:build": "npm run build --workspace=ui",
18
18
  "dashboard:start": "node ui/scripts/dashboard-start.cjs",
19
19
  "dashboard:open": "node ui/scripts/dashboard-open.cjs"
20
+ },
21
+ "allowScripts": {
22
+ "esbuild@0.21.5": true,
23
+ "fsevents@2.3.3": true
20
24
  }
21
25
  }
@@ -0,0 +1 @@
1
+ .loading-spinner{padding:2rem;text-align:center;color:#888;font-size:1rem}.error-message{padding:1rem;background:#fef2f2;border:1px solid #fca5a5;border-radius:4px;color:#b91c1c}.history-tab h2{margin-bottom:1rem}.history-empty{color:#888;padding:2rem;text-align:center}.history-list{list-style:none;padding:0;margin:0;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden}.history-entry{display:flex;align-items:center;gap:.75rem;padding:.65rem 1rem;cursor:pointer;border-bottom:1px solid #f3f4f6;transition:background .12s}.history-entry:last-child{border-bottom:none}.history-entry:hover,.history-entry:focus{background:#f0f7ff;outline:none}.history-entry.selected{background:#dbeafe}.history-badge{flex-shrink:0;font-size:.75rem;font-weight:600;padding:.2rem .55rem;border-radius:999px;white-space:nowrap}.badge-commit{background:#dbeafe;color:#1d4ed8}.badge-rapport{background:#dcfce7;color:#15803d}.history-subject{flex:1;font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#1f2937}.history-date{flex-shrink:0;font-size:.78rem;color:#9ca3af}.panel-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000040;z-index:100}.detail-panel{position:fixed;top:0;right:0;bottom:0;width:min(480px,100vw);background:#fff;box-shadow:-4px 0 24px #00000026;z-index:101;overflow-y:auto;padding:1.5rem;animation:slideIn .2s ease-out}@keyframes slideIn{0%{transform:translate(100%)}to{transform:translate(0)}}.panel-close{float:right;background:none;border:none;font-size:1.25rem;cursor:pointer;color:#6b7280;padding:.25rem;line-height:1}.panel-close:hover{color:#111827}.panel-title{margin:0 0 1rem;font-size:1.1rem;color:#1f2937}.panel-meta{display:grid;grid-template-columns:auto 1fr;gap:.3rem 1rem;font-size:.85rem;margin-bottom:1rem}.panel-meta dt{font-weight:600;color:#6b7280;white-space:nowrap}.panel-meta dd{margin:0;color:#1f2937;word-break:break-all}.panel-subject{font-size:.95rem;color:#1f2937}.panel-body{font-size:.82rem;white-space:pre-wrap;background:#f9fafb;border:1px solid #e5e7eb;border-radius:4px;padding:.75rem;max-height:40vh;overflow-y:auto}.panel-empty{color:#9ca3af;font-style:italic}.markdown-body{font-size:.9rem;line-height:1.6;color:#1f2937}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4{margin:1em 0 .4em;line-height:1.3}.markdown-body p{margin:.5em 0}.markdown-body code{background:#f3f4f6;padding:.1em .35em;border-radius:3px;font-size:.85em}.markdown-body pre{background:#f3f4f6;padding:.75rem;border-radius:4px;overflow-x:auto;font-size:.82em}.markdown-body pre code{background:none;padding:0}.markdown-body blockquote{border-left:3px solid #d1d5db;margin:.75em 0;padding:.25em 1em;color:#6b7280}.markdown-body ul,.markdown-body ol{padding-left:1.5em;margin:.5em 0}@media (max-width: 480px){.history-entry{flex-wrap:wrap;row-gap:var(--space-xs)}.history-subject{flex-basis:100%;order:3;white-space:normal}.detail-panel{box-sizing:border-box;padding:var(--space-md)}.panel-close{padding:var(--space-sm);font-size:1.5rem;display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:44px}.panel-meta{grid-template-columns:1fr;gap:.15rem 0}.panel-meta dt{margin-top:var(--space-xs)}.markdown-body code{overflow-wrap:break-word;word-break:break-word}.markdown-body table{display:block;max-width:100%;overflow-x:auto}}.board-view{display:flex;flex-direction:column;gap:1.5rem}.epic-card{background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:1rem}.epic-header{display:flex;align-items:center;gap:.75rem;margin-bottom:.75rem}.epic-title{font-size:1.1rem;font-weight:600;margin:0;cursor:pointer}.epic-id{color:#6b7280;font-size:.85rem}.stories-list{display:flex;flex-direction:column;gap:.75rem;padding-left:1rem}.story-card{background:#f9fafb;border:1px solid #e5e7eb;border-radius:4px;padding:.75rem}.story-header{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.story-title{font-size:1rem;font-weight:500;margin:0;cursor:pointer}.story-id{color:#6b7280;font-size:.8rem}.tasks-list{display:flex;flex-direction:column;gap:.4rem;padding-left:1rem}.task-card{background:#fff;border:1px solid #e5e7eb;border-radius:3px;padding:.5rem .75rem;display:flex;align-items:center;gap:.5rem}.task-title{font-size:.9rem;flex:1;cursor:pointer}.task-id{color:#9ca3af;font-size:.75rem}.task-assigned{color:#6b7280;font-size:.8rem}.status-badge{padding:.2rem .5rem;border-radius:9999px;font-size:.75rem;font-weight:500}.badge-pending{background:#f3f4f6;color:#374151}.badge-running{background:#dbeafe;color:#1d4ed8}.badge-in-progress{background:#ffedd5;color:#c2410c}.badge-passed{background:#dcfce7;color:#15803d}.badge-passed-remarks{background:#fff;color:#15803d;border:1px solid #dcfce7}.badge-done{background:#dbeafe;color:#1d4ed8}.badge-failed{background:#fee2e2;color:#b91c1c}.empty-state{padding:2rem;text-align:center;color:#6b7280}.status-filter{display:flex;flex-wrap:wrap;gap:.75rem;padding:.75rem 1rem;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px}.status-filter-option{display:flex;align-items:center;gap:.35rem;font-size:.85rem;color:#374151;cursor:pointer}.epic-range-filter{display:flex;flex-wrap:wrap;gap:.75rem;padding:.75rem 1rem;margin-bottom:.5rem;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px}.epic-range-option{display:flex;align-items:center;gap:.35rem;font-size:.85rem;color:#374151;cursor:pointer}.epic-range-option select{font-size:.85rem;color:#374151;padding:.15rem .35rem;border:1px solid #d1d5db;border-radius:4px;background:#fff;cursor:pointer}@media (max-width: 480px){.epic-header,.story-header{flex-wrap:wrap;row-gap:var(--space-xs)}.epic-title,.story-title{flex-basis:100%}.task-card{flex-wrap:wrap;row-gap:var(--space-xs)}.task-card .task-title{flex-basis:100%}.stories-list,.tasks-list{padding-left:var(--space-sm)}.epic-range-filter{flex-direction:column;align-items:stretch}.epic-range-option{justify-content:space-between}}.kanban-board{display:flex;align-items:flex-start;gap:.75rem;overflow-x:auto;padding-bottom:.5rem}.kanban-column{flex:0 0 220px;width:220px;background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:.6rem;display:flex;flex-direction:column;gap:.5rem}.kanban-column-empty{flex:0 0 140px;width:140px;opacity:.6}.kanban-column-header{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.kanban-column-title{font-size:.8rem;font-weight:600;color:#374151;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.kanban-column-count{font-size:.7rem;color:#6b7280;background:#e5e7eb;border-radius:9999px;padding:.05rem .4rem}.kanban-column-empty-state{font-size:.75rem;color:#9ca3af;text-align:center;padding:.5rem 0}.kanban-column-items{display:flex;flex-direction:column;gap:.5rem}.kanban-card{background:#fff;border:1px solid #e5e7eb;border-radius:4px;padding:.5rem .6rem;display:flex;flex-direction:column;gap:.3rem}.kanban-card-header{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.kanban-card-type{font-size:.65rem;text-transform:uppercase;letter-spacing:.03em;color:#9ca3af}.kanban-card-id{font-size:.7rem;color:#9ca3af}.kanban-card-title{font-size:.85rem;color:#111827;cursor:pointer}.kanban-card-queued{border-style:dashed;border-color:#c4b5fd;background:#fdfcff}.kanban-card-footer{display:flex;align-items:center;justify-content:space-between;gap:.4rem}.kanban-card-queued-marker{font-size:.65rem;text-transform:uppercase;letter-spacing:.03em;color:#6d28d9;background:#ede9fe;border:1px solid #ddd6fe;border-radius:9999px;padding:.05rem .4rem;white-space:nowrap;cursor:help}@media (max-width: 480px){.kanban-board{position:relative;gap:var(--space-sm);scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch}.kanban-column{flex:0 0 85%;width:85%;scroll-snap-align:start;scroll-snap-stop:always}.kanban-column-empty{flex:0 0 140px;width:140px}.kanban-board:after{content:"";position:sticky;top:0;right:0;flex:0 0 var(--space-xl);align-self:stretch;background:linear-gradient(to right,transparent,rgba(249,250,251,.95));pointer-events:none}}.architecture-tab{overflow-x:hidden}.architecture-tab h2{margin-bottom:1rem}.arch-empty{color:#9ca3af;font-style:italic}.arch-section{margin-bottom:2rem}.arch-section h3{font-size:1rem;font-weight:600;color:#374151;margin-bottom:.75rem;border-bottom:1px solid #e5e7eb;padding-bottom:.35rem}.tech-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.75rem}.tech-card{background:#fff;border:1px solid #e5e7eb;border-radius:8px;padding:.85rem 1rem;display:flex;flex-direction:column;gap:.3rem;box-shadow:0 1px 3px #0000000d}.tech-name{font-weight:600;font-size:.9rem;color:#1f2937}.tech-description{font-size:.78rem;color:#6b7280}.dep-table{width:100%;border-collapse:collapse;font-size:.875rem}.dep-table th{text-align:left;padding:.5rem .75rem;background:#f9fafb;border:1px solid #e5e7eb;font-weight:600;color:#374151}.dep-table td{padding:.45rem .75rem;border:1px solid #f3f4f6;color:#1f2937}.dep-table tr:hover td{background:#f9fafb}.dep-name{font-family:monospace}.dep-version{color:#6b7280;font-family:monospace}.dep-badge{display:inline-block;font-size:.7rem;font-weight:600;padding:.18rem .55rem;border-radius:999px;white-space:nowrap}.dep-runtime{background:#dbeafe;color:#1d4ed8}.dep-dev{background:#f3f4f6;color:#4b5563}.dep-table-wrap{overflow-x:auto}.sad-map-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;margin-bottom:.6rem}.sad-type-chip{--chip-color: #94a3b8;display:inline-flex;align-items:center;gap:.35rem;font-size:.75rem;font-weight:600;color:#374151;background:#fff;border:1px solid #e5e7eb;border-radius:999px;padding:.25rem .65rem;cursor:pointer;line-height:1.2}.sad-type-chip.is-collapsed{background:#f3f4f6;border-color:var(--chip-color)}.sad-type-chip-dot{width:8px;height:8px;border-radius:50%;background:var(--chip-color);flex-shrink:0}.sad-reset-view-btn{font-size:.75rem;font-weight:600;color:#374151;background:#fff;border:1px solid #e5e7eb;border-radius:6px;padding:.25rem .65rem;cursor:pointer;margin-left:auto}.sad-map-scroll{width:100%;min-height:320px;overflow:hidden;-webkit-overflow-scrolling:touch;border:1px solid var(--border, #e2e8f0);border-radius:8px;background:var(--bg-secondary, #f8fafc)}.sad-map-svg{display:block;width:100%;cursor:grab}.sad-map-svg:active{cursor:grabbing}.sad-empty{color:var(--text-muted, #94a3b8);font-style:italic}.sad-coverage-indicator{font-size:.78rem;color:#6b7280;margin:0 0 .5rem}.sad-coverage-shown{font-weight:600;color:#374151}.sad-coverage-hidden{color:#9ca3af}.sad-coverage-stale{color:#b45309;font-weight:600}.sad-empty-state{display:flex;flex-direction:column;gap:.35rem}.sad-empty-detail{font-size:.8rem;color:#9ca3af;margin:0}.sad-ghost-legend{display:flex;align-items:center;gap:.4rem;font-size:.75rem;color:#9ca3af;margin:0 0 .6rem}.sad-ghost-legend-swatch{display:inline-block;width:12px;height:12px;flex-shrink:0;border-radius:3px;background:#94a3b8;opacity:.4;border:1.5px dashed #1f2937}@media (max-width: 480px){.architecture-tab{padding-bottom:var(--space-sm)}.arch-section{margin-bottom:var(--space-lg)}.arch-section h3{padding-bottom:var(--space-xs)}.tech-grid{gap:var(--space-sm)}.tech-card{padding:var(--space-sm) var(--space-md)}.sad-map-scroll{min-height:180px}}.entry-list-empty{color:#888;padding:2rem;text-align:center}.entry-list{list-style:none;padding:0;margin:0;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden}.entry-list-item{display:flex;align-items:center;gap:.75rem;padding:.65rem 1rem;cursor:pointer;border-bottom:1px solid #f3f4f6;transition:background .12s}.entry-list-item:last-child{border-bottom:none}.entry-list-item:hover,.entry-list-item:focus{background:#f0f7ff;outline:none}.entry-list-item.selected{background:#dbeafe}.entry-badge{flex-shrink:0;font-size:.75rem;font-weight:600;padding:.2rem .55rem;border-radius:999px;white-space:nowrap;text-transform:capitalize;background:#ede9fe;color:#6d28d9}.entry-title{flex:1;font-size:.9rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#1f2937}.entry-date{flex-shrink:0;font-size:.78rem;color:#9ca3af}@media (max-width: 480px){.entry-list-item{flex-wrap:wrap;row-gap:var(--space-xs)}.entry-title{flex-basis:100%;order:3;white-space:normal}}.pagination-controls{display:flex;align-items:center;justify-content:center;gap:var(--space-md);margin-top:var(--space-md)}.pagination-btn{padding:.4rem .9rem;font-size:.85rem;border:1px solid #e5e7eb;border-radius:6px;background:#fff;color:#1f2937;cursor:pointer}.pagination-btn:hover:not(:disabled){background:#f0f7ff;border-color:#93c5fd}.pagination-btn:disabled{opacity:.45;cursor:not-allowed}.pagination-status{font-size:.82rem;color:#6b7280;white-space:nowrap}@media (max-width: 480px){.pagination-controls{gap:var(--space-sm)}.pagination-btn{padding:.5rem .85rem;font-size:.9rem}.pagination-status{font-size:.78rem}}.rapports-search-input{display:block;width:100%;box-sizing:border-box;padding:.55rem .75rem;margin-bottom:var(--space-md);font-size:.9rem;border:1px solid #e5e7eb;border-radius:6px;color:#1f2937}.rapports-search-input:focus{outline:none;border-color:#93c5fd;box-shadow:0 0 0 2px #3b82f640}@media (max-width: 480px){.rapports-search-input{font-size:1rem;padding:.65rem .75rem}}:root{--space-xs: .25rem;--space-sm: .5rem;--space-md: 1rem;--space-lg: 1.5rem;--space-xl: 2rem;--breakpoint-mobile: 480px;--breakpoint-tablet: 768px}body{font-family:sans-serif;margin:0;background:#f5f5f5}.app{max-width:1200px;margin:0 auto;padding:1rem}.tabs{display:flex;gap:0;border-bottom:1px solid #ddd}.tab-btn{padding:.6rem 1.2rem;border:none;background:transparent;cursor:pointer;font-size:1rem;color:#555;border-bottom:2px solid transparent}.tab-btn.active{color:#2563eb;border-bottom-color:#2563eb;font-weight:600}.tab-content{padding:1rem 0}@media (max-width: 480px){.app{width:100%;box-sizing:border-box;padding:var(--space-sm)}.tabs{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch}.tab-btn{flex:0 0 auto;padding:var(--space-sm) var(--space-md)}}