@jamie-tam/forge 6.0.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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +389 -0
  3. package/agents/architect.md +92 -0
  4. package/agents/builder.md +122 -0
  5. package/agents/code-reviewer.md +107 -0
  6. package/agents/concept-designer.md +207 -0
  7. package/agents/craft-reviewer.md +132 -0
  8. package/agents/critic.md +130 -0
  9. package/agents/doc-writer.md +85 -0
  10. package/agents/dreamer.md +129 -0
  11. package/agents/e2e-runner.md +89 -0
  12. package/agents/gotcha-hunter.md +127 -0
  13. package/agents/prototype-builder.md +193 -0
  14. package/agents/prototype-codifier.md +204 -0
  15. package/agents/prototype-reviewer.md +163 -0
  16. package/agents/security-reviewer.md +108 -0
  17. package/agents/spec-reviewer.md +94 -0
  18. package/agents/tracer.md +98 -0
  19. package/agents/wireframer.md +109 -0
  20. package/commands/abort.md +25 -0
  21. package/commands/bugfix.md +151 -0
  22. package/commands/evolve.md +118 -0
  23. package/commands/feature.md +236 -0
  24. package/commands/forge.md +100 -0
  25. package/commands/greenfield.md +185 -0
  26. package/commands/hotfix.md +98 -0
  27. package/commands/refactor.md +147 -0
  28. package/commands/resume.md +25 -0
  29. package/commands/setup.md +201 -0
  30. package/commands/status.md +27 -0
  31. package/commands/task-force.md +110 -0
  32. package/commands/validate.md +12 -0
  33. package/dist/__tests__/active-manifest.test.js +272 -0
  34. package/dist/__tests__/copy.test.js +96 -0
  35. package/dist/__tests__/gate-check.test.js +384 -0
  36. package/dist/__tests__/wiki.test.js +472 -0
  37. package/dist/__tests__/work-manifest.test.js +304 -0
  38. package/dist/active-manifest.js +229 -0
  39. package/dist/cli.js +158 -0
  40. package/dist/copy.js +124 -0
  41. package/dist/gate-check.js +326 -0
  42. package/dist/hooks.js +60 -0
  43. package/dist/init.js +140 -0
  44. package/dist/manifest.js +90 -0
  45. package/dist/merge.js +77 -0
  46. package/dist/paths.js +36 -0
  47. package/dist/uninstall.js +216 -0
  48. package/dist/update.js +158 -0
  49. package/dist/verify-manifest.js +65 -0
  50. package/dist/verify.js +98 -0
  51. package/dist/wiki-ui.js +310 -0
  52. package/dist/wiki.js +364 -0
  53. package/dist/work-manifest.js +798 -0
  54. package/hooks/config/gate-requirements.json +79 -0
  55. package/hooks/hooks.json +143 -0
  56. package/hooks/scripts/analyze-telemetry.sh +114 -0
  57. package/hooks/scripts/gate-enforcer.sh +164 -0
  58. package/hooks/scripts/pre-compact.sh +90 -0
  59. package/hooks/scripts/session-start.sh +81 -0
  60. package/hooks/scripts/telemetry.sh +41 -0
  61. package/hooks/scripts/wiki-lint.sh +87 -0
  62. package/hooks/templates/AGENTS.md.template +48 -0
  63. package/hooks/templates/CLAUDE.md.template +45 -0
  64. package/package.json +55 -0
  65. package/protocols/README.md +40 -0
  66. package/protocols/codex.md +151 -0
  67. package/protocols/graphify.md +156 -0
  68. package/references/common/agent-coordination.md +65 -0
  69. package/references/common/coding-standards.md +54 -0
  70. package/references/common/feature-tracking.md +21 -0
  71. package/references/common/io-protocol.md +36 -0
  72. package/references/common/phases.md +57 -0
  73. package/references/common/quality-gates.md +130 -0
  74. package/references/common/skill-authoring.md +154 -0
  75. package/references/common/skill-compliance.md +30 -0
  76. package/references/python/standards.md +44 -0
  77. package/references/react/standards.md +61 -0
  78. package/references/typescript/standards.md +42 -0
  79. package/rules/common/forge-system.md +59 -0
  80. package/rules/common/git-workflow.md +40 -0
  81. package/rules/common/guardrails.md +37 -0
  82. package/rules/common/quality-gates.md +18 -0
  83. package/rules/common/security.md +50 -0
  84. package/rules/common/skill-selection.md +78 -0
  85. package/rules/common/testing.md +58 -0
  86. package/rules/common/verification.md +39 -0
  87. package/skills/build-pr-workflow/SKILL.md +301 -0
  88. package/skills/build-pr-workflow/references/pr-template.md +62 -0
  89. package/skills/build-pr-workflow/references/subagent-merge.md +47 -0
  90. package/skills/build-pr-workflow/references/worktree-setup.md +125 -0
  91. package/skills/build-prototype/SKILL.md +264 -0
  92. package/skills/build-scaffold/SKILL.md +340 -0
  93. package/skills/build-tdd/SKILL.md +89 -0
  94. package/skills/build-wireframe/SKILL.md +110 -0
  95. package/skills/build-wireframe/assets/baseline-template.html +486 -0
  96. package/skills/build-wireframe/references/demo-walkthroughs.md +170 -0
  97. package/skills/build-wireframe/references/gotchas.md +188 -0
  98. package/skills/build-wireframe/references/legend-lines.md +141 -0
  99. package/skills/concept-slides/SKILL.md +192 -0
  100. package/skills/deliver-db-migration/SKILL.md +466 -0
  101. package/skills/deliver-deploy/SKILL.md +407 -0
  102. package/skills/deliver-onboarding/SKILL.md +198 -0
  103. package/skills/deliver-onboarding/references/document-templates.md +393 -0
  104. package/skills/deliver-onboarding/templates/getting-started.md +122 -0
  105. package/skills/discover-codebase-analysis/SKILL.md +448 -0
  106. package/skills/discover-requirements/SKILL.md +418 -0
  107. package/skills/discover-requirements/templates/prd.md +99 -0
  108. package/skills/discover-requirements/templates/technical-spec.md +123 -0
  109. package/skills/discover-requirements/templates/user-stories.md +76 -0
  110. package/skills/harden/SKILL.md +214 -0
  111. package/skills/iterate-prototype/SKILL.md +241 -0
  112. package/skills/plan-architecture/SKILL.md +457 -0
  113. package/skills/plan-architecture/templates/adr-template.md +52 -0
  114. package/skills/plan-architecture/templates/api-contract.md +99 -0
  115. package/skills/plan-architecture/templates/db-schema.md +81 -0
  116. package/skills/plan-architecture/templates/system-design.md +111 -0
  117. package/skills/plan-brainstorm/SKILL.md +433 -0
  118. package/skills/plan-design-system/SKILL.md +279 -0
  119. package/skills/plan-task-decompose/SKILL.md +454 -0
  120. package/skills/quality-code-review/SKILL.md +286 -0
  121. package/skills/quality-security-audit/SKILL.md +292 -0
  122. package/skills/quality-security-audit/references/audit-report-template.md +89 -0
  123. package/skills/quality-security-audit/references/owasp-checks.md +178 -0
  124. package/skills/quality-test-execution/SKILL.md +435 -0
  125. package/skills/quality-test-plan/SKILL.md +297 -0
  126. package/skills/quality-test-plan/references/test-type-guide.md +263 -0
  127. package/skills/quality-test-plan/templates/e2e-test-plan.md +72 -0
  128. package/skills/quality-test-plan/templates/integration-test-plan.md +74 -0
  129. package/skills/quality-test-plan/templates/load-test-plan.md +111 -0
  130. package/skills/quality-test-plan/templates/smoke-test-plan.md +68 -0
  131. package/skills/quality-test-plan/templates/unit-test-plan.md +56 -0
  132. package/skills/quality-uiux/SKILL.md +481 -0
  133. package/skills/support-debug/SKILL.md +464 -0
  134. package/skills/support-dream/SKILL.md +213 -0
  135. package/skills/support-gotcha/SKILL.md +249 -0
  136. package/skills/support-runtime-reachability/SKILL.md +190 -0
  137. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/app.ts +7 -0
  138. package/skills/support-runtime-reachability/scripts/__fixtures__/case-01-passes-app-use/src/handlers/cases.ts +7 -0
  139. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/app.ts +8 -0
  140. package/skills/support-runtime-reachability/scripts/__fixtures__/case-02-orphan-no-app-use/src/handlers/cases.ts +7 -0
  141. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/App.tsx +5 -0
  142. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/components/RingingBanner.tsx +7 -0
  143. package/skills/support-runtime-reachability/scripts/__fixtures__/case-03-orphan-import-only/src/hooks/useTwilio.ts +6 -0
  144. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/App.tsx +5 -0
  145. package/skills/support-runtime-reachability/scripts/__fixtures__/case-04-jsx-component-rendered/src/components/MyComp.tsx +3 -0
  146. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/App.tsx +3 -0
  147. package/skills/support-runtime-reachability/scripts/__fixtures__/case-05-jsx-component-not-rendered/src/components/Orphan.tsx +3 -0
  148. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/lib/Service.ts +6 -0
  149. package/skills/support-runtime-reachability/scripts/__fixtures__/case-06-class-instantiated/src/main.ts +4 -0
  150. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/lib/Lonely.ts +5 -0
  151. package/skills/support-runtime-reachability/scripts/__fixtures__/case-07-class-not-instantiated/src/main.ts +2 -0
  152. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/handler.ts +3 -0
  153. package/skills/support-runtime-reachability/scripts/__fixtures__/case-08-default-export-imported-and-called/src/main.ts +3 -0
  154. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/handler.ts +3 -0
  155. package/skills/support-runtime-reachability/scripts/__fixtures__/case-09-default-export-orphan/src/main.ts +2 -0
  156. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/lib.ts +5 -0
  157. package/skills/support-runtime-reachability/scripts/__fixtures__/case-10-aliased-named-export/src/main.ts +3 -0
  158. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/index.ts +1 -0
  159. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/lib/internal.ts +3 -0
  160. package/skills/support-runtime-reachability/scripts/__fixtures__/case-11-re-export-chain/src/main.ts +3 -0
  161. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.test.ts +5 -0
  162. package/skills/support-runtime-reachability/scripts/__fixtures__/case-12-test-only-caller/src/util.ts +3 -0
  163. package/skills/support-runtime-reachability/scripts/__fixtures__/case-13-gated-pending-annotation/src/future.ts +4 -0
  164. package/skills/support-runtime-reachability/scripts/__fixtures__/case-14-untraceable-annotation/src/decorated.ts +4 -0
  165. package/skills/support-runtime-reachability/scripts/__fixtures__/case-15-untraceable-empty/src/lazy.ts +4 -0
  166. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/lib.py +15 -0
  167. package/skills/support-runtime-reachability/scripts/__fixtures__/case-16-python-module/src/main.py +5 -0
  168. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/parent.ts +5 -0
  169. package/skills/support-runtime-reachability/scripts/__fixtures__/case-17-router-use/src/routes/cases.ts +5 -0
  170. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/lib/foo.ts +3 -0
  171. package/skills/support-runtime-reachability/scripts/__fixtures__/case-18-shadowed-name-fp/src/other.ts +8 -0
  172. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/cases.ts +4 -0
  173. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/handlers/users.ts +4 -0
  174. package/skills/support-runtime-reachability/scripts/__fixtures__/case-19-same-name-different-module/src/main.ts +5 -0
  175. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/handlers/cases.ts +3 -0
  176. package/skills/support-runtime-reachability/scripts/__fixtures__/case-20-aliased-import-usage/src/main.ts +4 -0
  177. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/lib.ts +5 -0
  178. package/skills/support-runtime-reachability/scripts/__fixtures__/case-21-mixed-default-and-named/src/main.ts +5 -0
  179. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/lib.ts +3 -0
  180. package/skills/support-runtime-reachability/scripts/__fixtures__/case-22-dynamic-import-then-caller/src/main.ts +8 -0
  181. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/lib.ts +3 -0
  182. package/skills/support-runtime-reachability/scripts/__fixtures__/case-23-dynamic-import-with-space/src/main.ts +7 -0
  183. package/skills/support-runtime-reachability/scripts/check.mjs +638 -0
  184. package/skills/support-runtime-reachability/scripts/check.test.mjs +244 -0
  185. package/skills/support-skill-validator/SKILL.md +194 -0
  186. package/skills/support-skill-validator/references/false-positives.md +59 -0
  187. package/skills/support-skill-validator/references/validation-checks.md +280 -0
  188. package/skills/support-system-guide/SKILL.md +311 -0
  189. package/skills/support-task-force/SKILL.md +265 -0
  190. package/skills/support-task-force/references/dispatch-pattern.md +178 -0
  191. package/skills/support-task-force/references/synthesis-template.md +126 -0
  192. package/skills/support-wiki-bootstrap/SKILL.md +37 -0
  193. package/skills/support-wiki-lint/SKILL.md +196 -0
  194. package/skills/support-wiki-lint/scripts/lint.mjs +488 -0
  195. package/skills/support-wiki-lint/scripts/lint.test.mjs +196 -0
  196. package/templates/README.md +23 -0
  197. package/templates/aiwiki/CLAUDE.md.template +78 -0
  198. package/templates/aiwiki/schemas/architecture.md +118 -0
  199. package/templates/aiwiki/schemas/convention.md +112 -0
  200. package/templates/aiwiki/schemas/decision.md +144 -0
  201. package/templates/aiwiki/schemas/gotcha.md +118 -0
  202. package/templates/aiwiki/schemas/oracle.md +105 -0
  203. package/templates/aiwiki/schemas/session.md +125 -0
  204. package/templates/manifests/bugfix.yaml +41 -0
  205. package/templates/manifests/feature.yaml +69 -0
  206. package/templates/manifests/greenfield.yaml +61 -0
  207. package/templates/manifests/hotfix.yaml +45 -0
  208. package/templates/manifests/refactor.yaml +44 -0
  209. package/templates/manifests/v5/SCHEMA.md +327 -0
  210. package/templates/manifests/v5/feature.yaml +77 -0
  211. package/templates/manifests/v6/SCHEMA.md +199 -0
  212. package/templates/wiki-html/dream-detail.html +378 -0
  213. package/templates/wiki-html/dreams-list.html +155 -0
@@ -0,0 +1,378 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Forge Wiki — Dream Detail</title>
6
+ <meta name="forge-token" content="{{FORGE_TOKEN}}">
7
+ <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
8
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
9
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css">
12
+ <script src="https://cdn.jsdelivr.net/npm/diff2html/bundles/js/diff2html.min.js"></script>
13
+ <style>
14
+ body { font-family: ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif; }
15
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
16
+ .d2h-wrapper { font-size: 13px; }
17
+ .d2h-file-header { background: #f5f5f4; padding: 8px 12px; border-bottom: 1px solid #e7e5e4; }
18
+ .d2h-file-list-wrapper { display: none; }
19
+ </style>
20
+ </head>
21
+ <body class="bg-stone-50 text-stone-900">
22
+ <div id="root"></div>
23
+ <script type="application/json" id="forge-state">{{FORGE_STATE}}</script>
24
+ <script type="text/babel" data-presets="react">
25
+ const STATE_TEXT = document.getElementById("forge-state").textContent;
26
+ let STATE;
27
+ try {
28
+ STATE = JSON.parse(STATE_TEXT);
29
+ } catch (_e) {
30
+ // Preview mode — placeholder unreplaced. Render with realistic sample dream.
31
+ STATE = {
32
+ _previewMode: true,
33
+ dream: {
34
+ has_conflicts: true,
35
+ manifest: {
36
+ dream_id: "preview-2026-05-11-1200-phase4-close",
37
+ trigger: "phase-close",
38
+ trigger_detail: "Phase 4 lock (feature/auth-refactor)",
39
+ summary: "Consolidates Phase 4's prototype-iteration captures: merges two silent-stub gotchas with the same root cause, promotes the route-handler-naming pattern (settled across 4 features) into a convention, and prunes one completed investigation note that ADR 0042 superseded.",
40
+ scope: ["aiwiki/raw/", "aiwiki/gotchas/", "aiwiki/conventions/"],
41
+ created_at: "2026-05-11T12:00:00Z",
42
+ provider: "forge-local",
43
+ changed_pages: 1,
44
+ new_pages: 1,
45
+ deleted_pages: 1,
46
+ lint_status: "passed",
47
+ review_status: "pending",
48
+ reviewed_at: null,
49
+ base_file_hashes: {
50
+ "aiwiki/gotchas/2026-05-09-stub-logger-recur.md": "sha256:abc123",
51
+ "aiwiki/raw/2026-05-01-old-investigation.md": "sha256:def456",
52
+ },
53
+ operations: [
54
+ {op: "merge", files: ["aiwiki/gotchas/2026-05-08-stub-logger.md", "aiwiki/gotchas/2026-05-09-stub-logger-recur.md"], into: "aiwiki/gotchas/2026-05-09-stub-logger-recur.md", reason: "duplicate root cause — same silent-stub pattern in two contexts"},
55
+ {op: "promote", from: "aiwiki/raw/2026-05-10-handler-naming.md", into: "aiwiki/conventions/handler-naming.md", reason: "matches convention schema; pattern settled across 4 features"},
56
+ {op: "prune", file: "aiwiki/raw/2026-05-01-old-investigation.md", reason: "investigation completed; outcome superseded by ADR 0042"},
57
+ ],
58
+ },
59
+ files: [
60
+ {
61
+ path: "conventions/handler-naming.md",
62
+ kind: "new",
63
+ diff: "diff --git a/dev/null b/aiwiki/conventions/handler-naming.md\nnew file mode 100644\n--- /dev/null\n+++ b/aiwiki/conventions/handler-naming.md\n@@ -0,0 +1,18 @@\n+---\n+schema_id: convention\n+schema_version: 1\n+scope: folder:src/routes\n+date: 2026-05-11\n+status: active\n+---\n+\n+## The convention\n+\n+Route handlers live in `src/routes/{resource}.ts` and export a `{resource}Router` constant.\n+\n+## Rationale\n+\n+Separates handler logic (testable in isolation) from routing (testable as integration).\n+\n+## Example\n+\n+[src/routes/users.ts:1-24@e5f6789](src/routes/users.ts) — usersRouter mounts three handlers.\n",
64
+ },
65
+ {
66
+ path: "gotchas/2026-05-09-stub-logger-recur.md",
67
+ kind: "changed",
68
+ diff: "diff --git a/aiwiki/gotchas/2026-05-09-stub-logger-recur.md b/aiwiki/gotchas/2026-05-09-stub-logger-recur.md\n--- a/aiwiki/gotchas/2026-05-09-stub-logger-recur.md\n+++ b/aiwiki/gotchas/2026-05-09-stub-logger-recur.md\n@@ -1,8 +1,10 @@\n ---\n schema_id: gotcha\n severity: high\n-date: 2026-05-09\n-occurrences: 1\n+date: 2026-05-09\n+occurrences: 2\n+merged_from: [aiwiki/gotchas/2026-05-08-stub-logger.md]\n status: active\n ---\n \n ## What broke\n",
69
+ conflict: {
70
+ expected_hash: "sha256:abc123",
71
+ actual_hash: "sha256:xyz789",
72
+ },
73
+ },
74
+ {
75
+ path: "raw/2026-05-01-old-investigation.md",
76
+ kind: "deleted",
77
+ diff: "diff --git a/aiwiki/raw/2026-05-01-old-investigation.md b/dev/null\ndeleted file mode 100644\n--- a/aiwiki/raw/2026-05-01-old-investigation.md\n+++ /dev/null\n@@ -1,12 +0,0 @@\n-# Investigation: cache layer choice\n-\n-Started 2026-05-01. Explored SQLite vs Postgres vs in-memory.\n-\n-Outcome: see ADR 0042-token-storage.md\n",
78
+ },
79
+ ],
80
+ },
81
+ };
82
+ }
83
+ const TOKEN = STATE._previewMode ? "preview-token" : (document.querySelector('meta[name="forge-token"]')?.content || "");
84
+
85
+ function lintBadgeClass(status) {
86
+ switch (status) {
87
+ case "passed": return "bg-emerald-100 text-emerald-800";
88
+ case "warnings": return "bg-amber-100 text-amber-800";
89
+ case "failed": return "bg-rose-100 text-rose-800";
90
+ default: return "bg-stone-100 text-stone-700";
91
+ }
92
+ }
93
+
94
+ function kindBadgeClass(kind) {
95
+ switch (kind) {
96
+ case "new": return "bg-emerald-100 text-emerald-800";
97
+ case "changed": return "bg-amber-100 text-amber-800";
98
+ case "deleted": return "bg-rose-100 text-rose-800";
99
+ default: return "bg-stone-100 text-stone-700";
100
+ }
101
+ }
102
+
103
+ function FileDiff({ file }) {
104
+ const containerRef = React.useRef(null);
105
+ React.useEffect(() => {
106
+ if (!containerRef.current) return;
107
+ if (file.diff && typeof Diff2Html !== "undefined") {
108
+ try {
109
+ const html = Diff2Html.html(file.diff, {
110
+ drawFileList: false,
111
+ matching: "lines",
112
+ outputFormat: "side-by-side",
113
+ colorScheme: "light",
114
+ });
115
+ containerRef.current.innerHTML = html;
116
+ } catch (err) {
117
+ containerRef.current.innerHTML = `<pre class="text-xs p-4 bg-stone-100 overflow-x-auto">${escapeHtml(file.diff)}</pre>`;
118
+ }
119
+ } else {
120
+ containerRef.current.innerHTML = `<div class="p-4 text-sm text-stone-500">No diff available (binary file or external change).</div>`;
121
+ }
122
+ }, [file.diff]);
123
+
124
+ const conflictBorder = file.conflict ? "border-rose-300 ring-2 ring-rose-100" : "border-stone-200";
125
+ const conflictHeader = file.conflict ? "bg-rose-50 border-rose-200" : "bg-stone-100 border-stone-200";
126
+
127
+ return (
128
+ <div className={`border ${conflictBorder} bg-white rounded-lg overflow-hidden mb-4`}>
129
+ <div className={`flex items-center gap-2 px-4 py-3 ${conflictHeader} border-b`}>
130
+ <span className={`badge ${kindBadgeClass(file.kind)}`}>{file.kind}</span>
131
+ <span className="font-mono text-sm text-stone-900 flex-1">{file.path}</span>
132
+ {file.conflict ? (
133
+ <span className="badge bg-rose-100 text-rose-800" title={`Expected hash: ${file.conflict.expected_hash}\nActual hash: ${file.conflict.actual_hash}`}>
134
+ conflict
135
+ </span>
136
+ ) : null}
137
+ </div>
138
+ {file.conflict ? (
139
+ <div className="px-4 py-2 bg-rose-50 border-b border-rose-200 text-xs text-rose-800">
140
+ This file changed in <span className="font-mono">aiwiki/</span> after the dream was created. Accepting would overwrite that change. Rerun the dream to incorporate the new state, or reject this dream.
141
+ </div>
142
+ ) : null}
143
+ <div ref={containerRef} className="overflow-x-auto" />
144
+ </div>
145
+ );
146
+ }
147
+
148
+ function escapeHtml(s) {
149
+ return String(s).replace(/[&<>"']/g, (c) => ({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]));
150
+ }
151
+
152
+ function OperationsLog({ operations }) {
153
+ if (!operations || operations.length === 0) {
154
+ return <div className="text-sm text-stone-500 italic">No operations recorded.</div>;
155
+ }
156
+ return (
157
+ <ul className="space-y-2">
158
+ {operations.map((op, i) => (
159
+ <li key={i} className="text-sm">
160
+ <span className="font-mono text-xs uppercase tracking-wide text-stone-500 mr-2">{op.op}</span>
161
+ {op.op === "merge" && op.files ? (
162
+ <span>
163
+ merged <span className="font-mono text-xs">{op.files.join(", ")}</span> → <span className="font-mono text-xs">{op.into}</span>
164
+ </span>
165
+ ) : op.op === "promote" ? (
166
+ <span>
167
+ promoted <span className="font-mono text-xs">{op.from}</span> → <span className="font-mono text-xs">{op.into}</span>
168
+ </span>
169
+ ) : op.op === "prune" ? (
170
+ <span>pruned <span className="font-mono text-xs">{op.file}</span></span>
171
+ ) : op.op === "addendum" ? (
172
+ <span>addendum to <span className="font-mono text-xs">{op.file}</span> ({op.addendum_section})</span>
173
+ ) : (
174
+ <span>{JSON.stringify(op)}</span>
175
+ )}
176
+ {op.reason ? <span className="text-stone-500"> — {op.reason}</span> : null}
177
+ </li>
178
+ ))}
179
+ </ul>
180
+ );
181
+ }
182
+
183
+ function PreviewBanner() {
184
+ return (
185
+ <div className="mb-6 p-3 bg-amber-50 border border-amber-200 rounded text-sm text-amber-900">
186
+ <span className="font-semibold">Preview mode.</span> Sample dream shown — this page expects to be served by <span className="font-mono text-xs bg-amber-100 px-1 py-0.5 rounded">forge wiki ui</span>, which loads real dreams from <span className="font-mono text-xs bg-amber-100 px-1 py-0.5 rounded">aiwiki/proposed/</span>. Accept and Reject buttons are disabled here.
187
+ </div>
188
+ );
189
+ }
190
+
191
+ function App() {
192
+ const { manifest, files, has_conflicts } = STATE.dream;
193
+ const [busy, setBusy] = React.useState(false);
194
+ const [error, setError] = React.useState(null);
195
+ const [showRejectForm, setShowRejectForm] = React.useState(false);
196
+ const [rejectReason, setRejectReason] = React.useState("");
197
+ const previewMode = !!STATE._previewMode;
198
+ const acceptBlocked = has_conflicts === true;
199
+
200
+ async function postAction(action, body = {}) {
201
+ if (previewMode) {
202
+ setError("Preview mode — actions disabled. Run `forge wiki ui` in your project to accept or reject for real.");
203
+ return;
204
+ }
205
+ setBusy(true);
206
+ setError(null);
207
+ try {
208
+ const res = await fetch(`/api/dreams/${encodeURIComponent(manifest.dream_id)}/${action}`, {
209
+ method: "POST",
210
+ headers: {
211
+ "content-type": "application/json",
212
+ "x-forge-token": TOKEN,
213
+ "x-forge-action": "1",
214
+ },
215
+ body: JSON.stringify(body),
216
+ });
217
+ const data = await res.json();
218
+ if (!res.ok) {
219
+ throw new Error(data.error || `HTTP ${res.status}`);
220
+ }
221
+ window.location.href = "/";
222
+ } catch (err) {
223
+ setError(err.message);
224
+ setBusy(false);
225
+ }
226
+ }
227
+
228
+ return (
229
+ <div className="max-w-5xl mx-auto p-8">
230
+ {previewMode ? <PreviewBanner /> : null}
231
+ <header className="mb-6 pb-6 border-b border-stone-200">
232
+ <div className="text-xs uppercase tracking-wide text-stone-500 mb-2">
233
+ <a href="/" className="hover:text-stone-700">← all dreams</a>
234
+ </div>
235
+ <h1 className="text-xl font-semibold text-stone-900 font-mono">{manifest.dream_id}</h1>
236
+ <div className="flex items-center gap-3 mt-3 flex-wrap">
237
+ <span className="badge bg-indigo-100 text-indigo-800">{manifest.trigger}</span>
238
+ <span className={`badge ${lintBadgeClass(manifest.lint_status)}`}>lint: {manifest.lint_status}</span>
239
+ <span className="text-xs text-stone-500">created {manifest.created_at}</span>
240
+ {manifest.provider ? (
241
+ <span className="text-xs text-stone-500">provider: {manifest.provider}</span>
242
+ ) : null}
243
+ </div>
244
+ {manifest.trigger_detail ? (
245
+ <p className="text-sm text-stone-600 mt-3">{manifest.trigger_detail}</p>
246
+ ) : null}
247
+ </header>
248
+
249
+ {manifest.summary ? (
250
+ <section className="mb-6 p-4 bg-white border-l-4 border-indigo-400 rounded-r">
251
+ <h2 className="text-xs uppercase tracking-wide text-stone-500 mb-2">Summary</h2>
252
+ <p className="text-sm text-stone-800 leading-relaxed">{manifest.summary}</p>
253
+ </section>
254
+ ) : null}
255
+
256
+ {acceptBlocked ? (
257
+ <section className="mb-6 p-4 bg-rose-50 border border-rose-300 rounded">
258
+ <div className="flex items-start gap-3">
259
+ <div className="text-rose-600 font-bold text-lg">⚠</div>
260
+ <div>
261
+ <div className="font-semibold text-rose-900">Concurrent changes detected — accept is blocked</div>
262
+ <p className="text-sm text-rose-800 mt-1">
263
+ One or more files this dream proposes to change have been modified in <span className="font-mono">aiwiki/</span> since the dream was created. Accepting would silently overwrite those changes. Rerun the dream to incorporate the new state, or reject and create a fresh one. Conflicting files are flagged below.
264
+ </p>
265
+ </div>
266
+ </div>
267
+ </section>
268
+ ) : null}
269
+
270
+ <section className="grid grid-cols-3 gap-4 mb-6">
271
+ <div className="border border-stone-200 bg-white rounded p-4 text-center">
272
+ <div className="text-2xl font-semibold text-emerald-700">+{manifest.new_pages}</div>
273
+ <div className="text-xs uppercase tracking-wide text-stone-500 mt-1">new</div>
274
+ </div>
275
+ <div className="border border-stone-200 bg-white rounded p-4 text-center">
276
+ <div className="text-2xl font-semibold text-amber-700">~{manifest.changed_pages}</div>
277
+ <div className="text-xs uppercase tracking-wide text-stone-500 mt-1">changed</div>
278
+ </div>
279
+ <div className="border border-stone-200 bg-white rounded p-4 text-center">
280
+ <div className="text-2xl font-semibold text-rose-700">−{manifest.deleted_pages}</div>
281
+ <div className="text-xs uppercase tracking-wide text-stone-500 mt-1">deleted</div>
282
+ </div>
283
+ </section>
284
+
285
+ {manifest.scope && manifest.scope.length > 0 ? (
286
+ <section className="mb-6">
287
+ <h2 className="text-sm uppercase tracking-wide text-stone-500 mb-2">Scope</h2>
288
+ <ul className="text-sm space-y-1">
289
+ {manifest.scope.map((s) => (
290
+ <li key={s} className="font-mono text-xs text-stone-700">{s}</li>
291
+ ))}
292
+ </ul>
293
+ </section>
294
+ ) : null}
295
+
296
+ <section className="mb-6">
297
+ <h2 className="text-sm uppercase tracking-wide text-stone-500 mb-2">Operations</h2>
298
+ <OperationsLog operations={manifest.operations} />
299
+ </section>
300
+
301
+ <section className="mb-8">
302
+ <h2 className="text-sm uppercase tracking-wide text-stone-500 mb-3">File changes ({files.length})</h2>
303
+ {files.length === 0 ? (
304
+ <div className="text-sm text-stone-500 italic">No file changes — operations only (e.g. prunes already applied).</div>
305
+ ) : (
306
+ files.map((f) => <FileDiff key={f.path} file={f} />)
307
+ )}
308
+ </section>
309
+
310
+ {error ? (
311
+ <div className="mb-4 p-3 bg-rose-50 border border-rose-200 rounded text-rose-800 text-sm">
312
+ {error}
313
+ </div>
314
+ ) : null}
315
+
316
+ <section className="sticky bottom-0 bg-stone-50/95 backdrop-blur border-t border-stone-200 -mx-8 px-8 py-4">
317
+ {showRejectForm ? (
318
+ <div className="space-y-3">
319
+ <label className="block text-sm">
320
+ <span className="text-stone-700">Reason for rejection (required):</span>
321
+ <textarea
322
+ value={rejectReason}
323
+ onChange={(e) => setRejectReason(e.target.value)}
324
+ rows={3}
325
+ className="mt-1 block w-full border border-stone-300 rounded p-2 text-sm font-mono"
326
+ placeholder="e.g. merged gotchas were not actually duplicates; root causes differ"
327
+ />
328
+ </label>
329
+ <div className="flex gap-3">
330
+ <button
331
+ disabled={busy || !rejectReason.trim()}
332
+ onClick={() => postAction("reject", { reason: rejectReason.trim() })}
333
+ className="px-4 py-2 bg-rose-600 hover:bg-rose-700 disabled:bg-stone-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded"
334
+ >
335
+ {busy ? "Rejecting..." : "Confirm reject"}
336
+ </button>
337
+ <button
338
+ disabled={busy}
339
+ onClick={() => { setShowRejectForm(false); setRejectReason(""); setError(null); }}
340
+ className="px-4 py-2 bg-white hover:bg-stone-100 disabled:cursor-not-allowed text-stone-700 text-sm font-medium rounded border border-stone-300"
341
+ >
342
+ Cancel
343
+ </button>
344
+ </div>
345
+ </div>
346
+ ) : (
347
+ <div className="flex gap-3">
348
+ <button
349
+ disabled={busy || acceptBlocked}
350
+ onClick={() => postAction("accept")}
351
+ title={acceptBlocked ? "Accept is blocked — conflicting files have changed in aiwiki/ since the dream was created" : ""}
352
+ className="px-5 py-2 bg-emerald-600 hover:bg-emerald-700 disabled:bg-stone-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded"
353
+ >
354
+ {busy ? "Accepting..." : acceptBlocked ? "Accept blocked (conflicts)" : "Accept dream"}
355
+ </button>
356
+ <button
357
+ disabled={busy}
358
+ onClick={() => setShowRejectForm(true)}
359
+ className="px-5 py-2 bg-white hover:bg-stone-100 disabled:cursor-not-allowed text-stone-700 text-sm font-medium rounded border border-stone-300"
360
+ >
361
+ Reject...
362
+ </button>
363
+ </div>
364
+ )}
365
+ <p className="text-xs text-stone-500 mt-2">
366
+ {acceptBlocked
367
+ ? "Conflict resolution: rerun the dream so the consolidator sees the current aiwiki/ state, or reject this dream and let the next phase-close cycle produce a fresh one."
368
+ : "Accept performs an atomic swap of all proposed files into aiwiki/; the original state is archived to .forge/wiki-history/. Reject discards the proposal and logs the reason."}
369
+ </p>
370
+ </section>
371
+ </div>
372
+ );
373
+ }
374
+
375
+ ReactDOM.createRoot(document.getElementById("root")).render(<App />);
376
+ </script>
377
+ </body>
378
+ </html>
@@ -0,0 +1,155 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Forge Wiki — Dream Review</title>
6
+ <meta name="forge-token" content="{{FORGE_TOKEN}}">
7
+ <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
8
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
9
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <style>
12
+ body { font-family: ui-sans-serif, -apple-system, "Segoe UI", system-ui, sans-serif; }
13
+ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
14
+ </style>
15
+ </head>
16
+ <body class="bg-stone-50 text-stone-900">
17
+ <div id="root"></div>
18
+ <script type="application/json" id="forge-state">{{FORGE_STATE}}</script>
19
+ <script type="text/babel" data-presets="react">
20
+ const STATE_TEXT = document.getElementById("forge-state").textContent;
21
+ let STATE;
22
+ try {
23
+ STATE = JSON.parse(STATE_TEXT);
24
+ } catch (_e) {
25
+ // Preview mode — placeholder unreplaced. Render with sample data + banner.
26
+ STATE = {
27
+ _previewMode: true,
28
+ dreams: [
29
+ {
30
+ dream_id: "preview-2026-05-11-1200-phase4-close",
31
+ trigger: "phase-close",
32
+ trigger_detail: "Phase 4 lock (feature/auth-refactor)",
33
+ created_at: "2026-05-11T12:00:00Z",
34
+ age_human: "5m",
35
+ changed_pages: 2,
36
+ new_pages: 1,
37
+ deleted_pages: 1,
38
+ lint_status: "passed",
39
+ scope: ["aiwiki/raw/", "aiwiki/gotchas/", "aiwiki/conventions/"],
40
+ },
41
+ {
42
+ dream_id: "preview-2026-05-10-0830-precompact",
43
+ trigger: "pre-compact",
44
+ trigger_detail: "85% context threshold",
45
+ created_at: "2026-05-10T08:30:00Z",
46
+ age_human: "1d",
47
+ changed_pages: 0,
48
+ new_pages: 0,
49
+ deleted_pages: 3,
50
+ lint_status: "warnings",
51
+ scope: ["aiwiki/sessions/", "aiwiki/raw/"],
52
+ },
53
+ ],
54
+ };
55
+ }
56
+
57
+ function lintBadgeClass(status) {
58
+ switch (status) {
59
+ case "passed": return "bg-emerald-100 text-emerald-800";
60
+ case "warnings": return "bg-amber-100 text-amber-800";
61
+ case "failed": return "bg-rose-100 text-rose-800";
62
+ default: return "bg-stone-100 text-stone-700";
63
+ }
64
+ }
65
+
66
+ function triggerBadgeClass(trigger) {
67
+ switch (trigger) {
68
+ case "phase-close": return "bg-indigo-100 text-indigo-800";
69
+ case "pre-compact": return "bg-blue-100 text-blue-800";
70
+ case "manual": return "bg-stone-100 text-stone-700";
71
+ default: return "bg-stone-100 text-stone-700";
72
+ }
73
+ }
74
+
75
+ function DreamCard({ dream }) {
76
+ return (
77
+ <a
78
+ href={`/dreams/${encodeURIComponent(dream.dream_id)}`}
79
+ className="block border border-stone-200 bg-white rounded-lg p-5 hover:border-stone-400 hover:shadow-sm transition"
80
+ >
81
+ <div className="flex items-start justify-between gap-4">
82
+ <div className="min-w-0 flex-1">
83
+ <div className="flex items-center gap-2 mb-1">
84
+ <span className={`badge ${triggerBadgeClass(dream.trigger)}`}>{dream.trigger}</span>
85
+ <span className={`badge ${lintBadgeClass(dream.lint_status)}`}>lint: {dream.lint_status}</span>
86
+ <span className="text-xs text-stone-500">{dream.age_human}</span>
87
+ </div>
88
+ <div className="font-mono text-sm text-stone-900 truncate">{dream.dream_id}</div>
89
+ {dream.trigger_detail ? (
90
+ <div className="text-sm text-stone-600 mt-1">{dream.trigger_detail}</div>
91
+ ) : null}
92
+ {dream.scope && dream.scope.length > 0 ? (
93
+ <div className="text-xs text-stone-500 mt-2 truncate">
94
+ <span className="text-stone-400">scope: </span>{dream.scope.join(", ")}
95
+ </div>
96
+ ) : null}
97
+ </div>
98
+ <div className="text-right text-sm flex-shrink-0">
99
+ <div className="font-semibold text-stone-900">
100
+ {dream.changed_pages + dream.new_pages + dream.deleted_pages} files
101
+ </div>
102
+ <div className="text-xs text-stone-500 mt-1 space-x-2">
103
+ <span className="text-emerald-600">+{dream.new_pages}</span>
104
+ <span className="text-amber-600">~{dream.changed_pages}</span>
105
+ <span className="text-rose-600">−{dream.deleted_pages}</span>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </a>
110
+ );
111
+ }
112
+
113
+ function PreviewBanner() {
114
+ return (
115
+ <div className="mb-6 p-3 bg-amber-50 border border-amber-200 rounded text-sm text-amber-900">
116
+ <span className="font-semibold">Preview mode.</span> Sample data shown — this page expects to be served by <span className="font-mono text-xs bg-amber-100 px-1 py-0.5 rounded">forge wiki ui</span>, which injects real state from <span className="font-mono text-xs bg-amber-100 px-1 py-0.5 rounded">aiwiki/proposed/</span>. Run <span className="font-mono text-xs bg-amber-100 px-1 py-0.5 rounded">forge wiki ui</span> in your project to see live dreams.
117
+ </div>
118
+ );
119
+ }
120
+
121
+ function App() {
122
+ const dreams = STATE.dreams || [];
123
+ return (
124
+ <div className="max-w-4xl mx-auto p-8">
125
+ {STATE._previewMode ? <PreviewBanner /> : null}
126
+ <header className="mb-8 pb-6 border-b border-stone-200">
127
+ <div className="text-xs uppercase tracking-wide text-stone-500 mb-2">forge wiki</div>
128
+ <h1 className="text-2xl font-semibold text-stone-900">Pending dream review</h1>
129
+ <p className="text-sm text-stone-600 mt-2">
130
+ {dreams.length === 0
131
+ ? "No pending dreams. The wiki is up to date."
132
+ : `${dreams.length} pending ${dreams.length === 1 ? "dream" : "dreams"} awaiting your review.`}
133
+ </p>
134
+ </header>
135
+ {dreams.length === 0 ? (
136
+ <div className="text-center py-12 text-stone-500">
137
+ <div className="text-5xl mb-4">✓</div>
138
+ <div>All clear. New dreams will appear here as they fire (phase close, pre-compact, manual).</div>
139
+ </div>
140
+ ) : (
141
+ <div className="space-y-3">
142
+ {dreams.map((d) => <DreamCard key={d.dream_id} dream={d} />)}
143
+ </div>
144
+ )}
145
+ <footer className="mt-12 pt-6 border-t border-stone-200 text-xs text-stone-500">
146
+ Forge wiki ui — dreams listed oldest first. Click a dream to review and accept/reject. Server stops on Ctrl-C in the launching terminal.
147
+ </footer>
148
+ </div>
149
+ );
150
+ }
151
+
152
+ ReactDOM.createRoot(document.getElementById("root")).render(<App />);
153
+ </script>
154
+ </body>
155
+ </html>