@entelligentsia/forgecli 0.11.2 → 0.15.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 (89) hide show
  1. package/CHANGELOG.md +324 -0
  2. package/README.md +2 -1
  3. package/dist/CHANGELOG-forge-plugin.md +210 -0
  4. package/dist/bin/forge.js +20 -1
  5. package/dist/bin/forge.js.map +1 -1
  6. package/dist/extensions/forgecli/ask-user-tool.js +32 -20
  7. package/dist/extensions/forgecli/ask-user-tool.js.map +1 -1
  8. package/dist/extensions/forgecli/config-layer.d.ts +15 -0
  9. package/dist/extensions/forgecli/config-layer.js +4 -1
  10. package/dist/extensions/forgecli/config-layer.js.map +1 -1
  11. package/dist/extensions/forgecli/config-writer.js +4 -1
  12. package/dist/extensions/forgecli/config-writer.js.map +1 -1
  13. package/dist/extensions/forgecli/enhance.js +1 -1
  14. package/dist/extensions/forgecli/enhance.js.map +1 -1
  15. package/dist/extensions/forgecli/fix-bug.js +31 -1
  16. package/dist/extensions/forgecli/fix-bug.js.map +1 -1
  17. package/dist/extensions/forgecli/forge-cli-schema.json +19 -0
  18. package/dist/extensions/forgecli/forge-tools.js +80 -0
  19. package/dist/extensions/forgecli/forge-tools.js.map +1 -1
  20. package/dist/extensions/forgecli/forge-update-command.js +24 -18
  21. package/dist/extensions/forgecli/forge-update-command.js.map +1 -1
  22. package/dist/extensions/forgecli/friction-emit.d.ts +97 -0
  23. package/dist/extensions/forgecli/friction-emit.js +246 -0
  24. package/dist/extensions/forgecli/friction-emit.js.map +1 -0
  25. package/dist/extensions/forgecli/health-check.d.ts +10 -0
  26. package/dist/extensions/forgecli/health-check.js +160 -8
  27. package/dist/extensions/forgecli/health-check.js.map +1 -1
  28. package/dist/extensions/forgecli/hook-dispatcher.js +24 -2
  29. package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
  30. package/dist/extensions/forgecli/hooks/write-guard.js +5 -1
  31. package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -1
  32. package/dist/extensions/forgecli/index.js +29 -5
  33. package/dist/extensions/forgecli/index.js.map +1 -1
  34. package/dist/extensions/forgecli/lib/store-error-remediation.d.ts +65 -0
  35. package/dist/extensions/forgecli/lib/store-error-remediation.js +298 -0
  36. package/dist/extensions/forgecli/lib/store-error-remediation.js.map +1 -0
  37. package/dist/extensions/forgecli/regenerate.d.ts +22 -0
  38. package/dist/extensions/forgecli/regenerate.js +133 -3
  39. package/dist/extensions/forgecli/regenerate.js.map +1 -1
  40. package/dist/extensions/forgecli/run-sprint.js +16 -1
  41. package/dist/extensions/forgecli/run-sprint.js.map +1 -1
  42. package/dist/extensions/forgecli/run-task.js +30 -8
  43. package/dist/extensions/forgecli/run-task.js.map +1 -1
  44. package/dist/extensions/forgecli/skill-curation-flag.d.ts +21 -0
  45. package/dist/extensions/forgecli/skill-curation-flag.js +71 -0
  46. package/dist/extensions/forgecli/skill-curation-flag.js.map +1 -0
  47. package/dist/extensions/forgecli/skill-curator-subagent.d.ts +101 -0
  48. package/dist/extensions/forgecli/skill-curator-subagent.js +342 -0
  49. package/dist/extensions/forgecli/skill-curator-subagent.js.map +1 -0
  50. package/dist/extensions/forgecli/skill-retriever.d.ts +84 -0
  51. package/dist/extensions/forgecli/skill-retriever.js +246 -0
  52. package/dist/extensions/forgecli/skill-retriever.js.map +1 -0
  53. package/dist/extensions/forgecli/skill-usage-tracker.d.ts +91 -0
  54. package/dist/extensions/forgecli/skill-usage-tracker.js +224 -0
  55. package/dist/extensions/forgecli/skill-usage-tracker.js.map +1 -0
  56. package/dist/extensions/forgecli/store-resolver.d.ts +18 -0
  57. package/dist/extensions/forgecli/store-resolver.js +44 -4
  58. package/dist/extensions/forgecli/store-resolver.js.map +1 -1
  59. package/dist/extensions/forgecli/store-validator.d.ts +3 -0
  60. package/dist/extensions/forgecli/store-validator.js +4 -2
  61. package/dist/extensions/forgecli/store-validator.js.map +1 -1
  62. package/dist/forge-payload/.base-pack/personas/supervisor.md +9 -0
  63. package/dist/forge-payload/.base-pack/workflows/enhance.md +344 -18
  64. package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
  65. package/dist/forge-payload/.schemas/event.schema.json +20 -2
  66. package/dist/forge-payload/.schemas/migrations.json +112 -0
  67. package/dist/forge-payload/.schemas/proposal.schema.json +40 -0
  68. package/dist/forge-payload/agents/store-query-validator.md +103 -0
  69. package/dist/forge-payload/agents/tomoshibi.md +185 -0
  70. package/dist/forge-payload/commands/regenerate.md +109 -20
  71. package/dist/forge-payload/hooks/check-update.js +378 -0
  72. package/dist/forge-payload/hooks/forge-permissions.js +158 -0
  73. package/dist/forge-payload/hooks/triage-error.js +71 -0
  74. package/dist/forge-payload/hooks/validate-write.js +236 -0
  75. package/dist/forge-payload/integrity.json +32 -0
  76. package/dist/forge-payload/meta/workflows/meta-enhance.md +344 -18
  77. package/dist/forge-payload/schemas/structure-manifest.json +511 -0
  78. package/dist/forge-payload/tools/build-persona-pack.cjs +120 -11
  79. package/dist/forge-payload/tools/compression-gate.cjs +192 -0
  80. package/dist/forge-payload/tools/delete-candidate-detector.cjs +114 -0
  81. package/dist/forge-payload/tools/judge-proposal.cjs +177 -0
  82. package/dist/forge-payload/tools/manage-versions.cjs +132 -4
  83. package/dist/forge-payload/tools/queue-drain.cjs +152 -0
  84. package/dist/forge-payload/tools/replay-scoring.cjs +117 -0
  85. package/node_modules/@mariozechner/clipboard/package.json +2 -1
  86. package/node_modules/@mariozechner/clipboard-linux-x64-musl/README.md +3 -0
  87. package/node_modules/@mariozechner/clipboard-linux-x64-musl/clipboard.linux-x64-musl.node +0 -0
  88. package/node_modules/@mariozechner/clipboard-linux-x64-musl/package.json +25 -0
  89. package/package.json +4 -2
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+ // FORGE-S24-T07 — queue drain at sprint close (per-task curator → batched review).
3
+ //
4
+ // Paper §3.2.1 grouped reward: one batched review at sprint close, not one
5
+ // prompt per task. Per-task curators (T10) append proposals to a project-local
6
+ // queue as they encounter friction during the sprint; Phase 2 drains the queue,
7
+ // dedupes by {op, target_path, body-hash}, and feeds a single batch into the
8
+ // downstream pipeline (compression gate T06 → replay scoring T04 → judge T03).
9
+ //
10
+ // Queue layout (canonical):
11
+ // .forge/enhancement-proposals/queue/<sprintId>/<taskId>-<ts>.json
12
+ //
13
+ // - One file per per-task curator run.
14
+ // - The `<ts>` suffix (ISO compact timestamp) means concurrent or repeated
15
+ // curator runs on the same task never collide; each run produces a fresh
16
+ // file. Files are append-only — once written, never overwritten. The drain
17
+ // is a read-only pass that does NOT delete the queue (operators triage
18
+ // queue files for retrospective debugging).
19
+ //
20
+ // Dedup key: `<op>|<target_path>|sha256(diff_body)`.
21
+ // - `op` and `target_path` together identify "what artifact and how" (insert
22
+ // vs update vs delete).
23
+ // - `sha256(diff_body)` makes two proposals with byte-identical diff bodies
24
+ // collapse to one even if their rationales or sourceFrictionIds differ.
25
+ // Different bodies → different proposals (a smaller surgical patch and a
26
+ // full-file rewrite are distinct decisions for the judge).
27
+ //
28
+ // Pure helpers (`bodyHash`, `dedupeKey`, `dedupeProposals`, `queuePathFor`) are
29
+ // fs-free. `appendToQueue` and `drainQueue` touch the filesystem.
30
+ //
31
+ // Exports:
32
+ // bodyHash(body) → sha256 hex digest of body (UTF-8).
33
+ // dedupeKey(proposal) → composite key string.
34
+ // dedupeProposals(proposals) → first-seen-wins deduped array.
35
+ // queuePathFor({ queueRoot, sprintId, taskId, ts })
36
+ // → canonical file path (string).
37
+ // appendToQueue({ queueRoot, sprintId, taskId, ts, proposals })
38
+ // → absolute path written; throws if path
39
+ // already exists (append-only invariant).
40
+ // drainQueue({ queueRoot, sprintId })
41
+ // → { proposals: [...deduped], files: [...],
42
+ // errors: [...] } — fs read, never write.
43
+
44
+ const fs = require('node:fs');
45
+ const path = require('node:path');
46
+ const crypto = require('node:crypto');
47
+
48
+ function bodyHash(body) {
49
+ if (typeof body !== 'string') {
50
+ throw new TypeError('bodyHash: body must be a string');
51
+ }
52
+ return crypto.createHash('sha256').update(body, 'utf8').digest('hex');
53
+ }
54
+
55
+ function dedupeKey(proposal) {
56
+ if (!proposal || typeof proposal !== 'object') {
57
+ throw new TypeError('dedupeKey: proposal must be an object');
58
+ }
59
+ const op = String(proposal.op ?? '');
60
+ const target_path = String(proposal.target_path ?? '');
61
+ const diff_body = String(proposal.diff_body ?? '');
62
+ return `${op}|${target_path}|${bodyHash(diff_body)}`;
63
+ }
64
+
65
+ function dedupeProposals(proposals) {
66
+ if (!Array.isArray(proposals)) {
67
+ throw new TypeError('dedupeProposals: proposals must be an array');
68
+ }
69
+ const seen = new Set();
70
+ const out = [];
71
+ for (const p of proposals) {
72
+ const k = dedupeKey(p);
73
+ if (seen.has(k)) continue;
74
+ seen.add(k);
75
+ out.push(p);
76
+ }
77
+ return out;
78
+ }
79
+
80
+ function queuePathFor({ queueRoot, sprintId, taskId, ts } = {}) {
81
+ if (typeof sprintId !== 'string' || !sprintId) {
82
+ throw new TypeError('queuePathFor: sprintId is required');
83
+ }
84
+ if (typeof taskId !== 'string' || !taskId) {
85
+ throw new TypeError('queuePathFor: taskId is required');
86
+ }
87
+ if (typeof ts !== 'string' || !ts) {
88
+ throw new TypeError('queuePathFor: ts is required');
89
+ }
90
+ const root = queueRoot ?? '.forge/enhancement-proposals/queue';
91
+ return path.join(root, sprintId, `${taskId}-${ts}.json`);
92
+ }
93
+
94
+ function appendToQueue({ queueRoot, sprintId, taskId, ts, proposals } = {}) {
95
+ if (!Array.isArray(proposals)) {
96
+ throw new TypeError('appendToQueue: proposals must be an array');
97
+ }
98
+ const target = queuePathFor({ queueRoot, sprintId, taskId, ts });
99
+ if (fs.existsSync(target)) {
100
+ throw new Error(
101
+ `appendToQueue: queue file already exists at ${target} ` +
102
+ `(queue is append-only; choose a fresh ts).`,
103
+ );
104
+ }
105
+ fs.mkdirSync(path.dirname(target), { recursive: true });
106
+ fs.writeFileSync(target, JSON.stringify(proposals, null, 2), 'utf8');
107
+ return target;
108
+ }
109
+
110
+ function drainQueue({ queueRoot, sprintId } = {}) {
111
+ if (typeof sprintId !== 'string' || !sprintId) {
112
+ throw new TypeError('drainQueue: sprintId is required');
113
+ }
114
+ if (typeof queueRoot !== 'string' || !queueRoot) {
115
+ throw new TypeError('drainQueue: queueRoot is required');
116
+ }
117
+ const sprintDir = path.join(queueRoot, sprintId);
118
+ const result = { proposals: [], files: [], errors: [] };
119
+ if (!fs.existsSync(sprintDir)) return result;
120
+
121
+ const entries = fs.readdirSync(sprintDir)
122
+ .filter((name) => name.endsWith('.json'))
123
+ .sort(); // deterministic order: lexicographic ⇒ chronological because of ts suffix
124
+ const merged = [];
125
+ for (const name of entries) {
126
+ const abs = path.join(sprintDir, name);
127
+ result.files.push(abs);
128
+ let parsed;
129
+ try {
130
+ parsed = JSON.parse(fs.readFileSync(abs, 'utf8'));
131
+ } catch (err) {
132
+ result.errors.push({ file: abs, error: err.message });
133
+ continue;
134
+ }
135
+ if (!Array.isArray(parsed)) {
136
+ result.errors.push({ file: abs, error: 'expected JSON array of proposals' });
137
+ continue;
138
+ }
139
+ for (const p of parsed) merged.push(p);
140
+ }
141
+ result.proposals = dedupeProposals(merged);
142
+ return result;
143
+ }
144
+
145
+ module.exports = {
146
+ bodyHash,
147
+ dedupeKey,
148
+ dedupeProposals,
149
+ queuePathFor,
150
+ appendToQueue,
151
+ drainQueue,
152
+ };
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+ // FORGE-S24-T04 — cross-task replay scoring (recurrence boost).
3
+ //
4
+ // For each enhancement proposal synthesised from a friction event at task `t`,
5
+ // scan friction events in tasks `t+1..N` (within the same sprint) for the
6
+ // same `(subkind, evidence.skillId)` pair. Surface the count + task list on
7
+ // the proposal so the T03 judge sees "this friction happened 3 times" rather
8
+ // than "this friction happened once".
9
+ //
10
+ // Pure module; consumed by Phase 2 step 5/6 of meta-enhance.md after the
11
+ // dedup pass and before the proposal artifact is written.
12
+ //
13
+ // Exports:
14
+ // computeRecurrence({ events, subkind, skillId, fromTaskId, taskOrder })
15
+ // -> { recurrence_count, recurrence_task_ids }
16
+ // annotateProposals(proposals, frictionEvents, taskOrder)
17
+ // -> new array of proposals, each carrying recurrence_count +
18
+ // recurrence_task_ids.
19
+
20
+ function computeRecurrence({ events, subkind, skillId, fromTaskId, taskOrder }) {
21
+ if (!Array.isArray(events)) throw new TypeError('events must be an array');
22
+ if (!Array.isArray(taskOrder)) throw new TypeError('taskOrder must be an array');
23
+ if (typeof subkind !== 'string' || subkind === '') throw new TypeError('subkind required');
24
+ if (typeof skillId !== 'string' || skillId === '') throw new TypeError('skillId required');
25
+ if (typeof fromTaskId !== 'string' || fromTaskId === '') throw new TypeError('fromTaskId required');
26
+
27
+ const originIdx = taskOrder.indexOf(fromTaskId);
28
+
29
+ // Distinct taskIds with a matching friction event.
30
+ const matchingTaskIds = new Set();
31
+
32
+ for (const evt of events) {
33
+ if (!evt || evt.type !== 'friction') continue;
34
+ if (evt.subkind !== subkind) continue;
35
+ const evtSkillId = evt.evidence && evt.evidence.skillId;
36
+ if (evtSkillId !== skillId) continue;
37
+ if (!evt.taskId) continue;
38
+
39
+ if (originIdx === -1) {
40
+ // fromTaskId not in taskOrder: only count the origin task itself.
41
+ if (evt.taskId === fromTaskId) matchingTaskIds.add(evt.taskId);
42
+ continue;
43
+ }
44
+
45
+ const evtIdx = taskOrder.indexOf(evt.taskId);
46
+ // Forward-only: include origin task (evtIdx === originIdx) and later
47
+ // (evtIdx > originIdx). Skip earlier and unknown tasks.
48
+ if (evtIdx >= originIdx) matchingTaskIds.add(evt.taskId);
49
+ }
50
+
51
+ // Always include the origin task so recurrence_count >= 1.
52
+ matchingTaskIds.add(fromTaskId);
53
+
54
+ // Sort by taskOrder position; tasks not in taskOrder go last in insertion order.
55
+ const ordered = [...matchingTaskIds].sort((a, b) => {
56
+ const ai = taskOrder.indexOf(a);
57
+ const bi = taskOrder.indexOf(b);
58
+ if (ai === -1 && bi === -1) return 0;
59
+ if (ai === -1) return 1;
60
+ if (bi === -1) return -1;
61
+ return ai - bi;
62
+ });
63
+
64
+ return {
65
+ recurrence_count: ordered.length,
66
+ recurrence_task_ids: ordered,
67
+ };
68
+ }
69
+
70
+ function annotateProposals(proposals, frictionEvents, taskOrder) {
71
+ if (!Array.isArray(proposals)) throw new TypeError('proposals must be an array');
72
+ if (!Array.isArray(frictionEvents)) throw new TypeError('frictionEvents must be an array');
73
+ if (!Array.isArray(taskOrder)) throw new TypeError('taskOrder must be an array');
74
+
75
+ // Index friction events by eventId for O(1) sourceFrictionIds resolution.
76
+ const byEventId = new Map();
77
+ for (const evt of frictionEvents) {
78
+ if (evt && evt.eventId) byEventId.set(evt.eventId, evt);
79
+ }
80
+
81
+ return proposals.map((proposal) => {
82
+ const sourceIds = Array.isArray(proposal.sourceFrictionIds)
83
+ ? proposal.sourceFrictionIds
84
+ : [];
85
+
86
+ // Resolve the first sourceFrictionId that points at a known friction event
87
+ // with subkind + evidence.skillId. The originating event determines
88
+ // (subkind, skillId, fromTaskId) for the recurrence scan.
89
+ let originEvent = null;
90
+ for (const id of sourceIds) {
91
+ const evt = byEventId.get(id);
92
+ if (evt && evt.subkind && evt.evidence && evt.evidence.skillId && evt.taskId) {
93
+ originEvent = evt;
94
+ break;
95
+ }
96
+ }
97
+
98
+ if (!originEvent) {
99
+ // No resolvable provenance: neutral annotation. recurrence_count=1
100
+ // keeps the schema invariant; recurrence_task_ids is empty so the
101
+ // judge can distinguish "single observation" from "unresolved origin".
102
+ return { ...proposal, recurrence_count: 1, recurrence_task_ids: [] };
103
+ }
104
+
105
+ const { recurrence_count, recurrence_task_ids } = computeRecurrence({
106
+ events: frictionEvents,
107
+ subkind: originEvent.subkind,
108
+ skillId: originEvent.evidence.skillId,
109
+ fromTaskId: originEvent.taskId,
110
+ taskOrder,
111
+ });
112
+
113
+ return { ...proposal, recurrence_count, recurrence_task_ids };
114
+ });
115
+ }
116
+
117
+ module.exports = { computeRecurrence, annotateProposals };
@@ -42,6 +42,7 @@
42
42
  "version": "napi version"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@mariozechner/clipboard-linux-x64-gnu": "0.3.6"
45
+ "@mariozechner/clipboard-linux-x64-gnu": "0.3.6",
46
+ "@mariozechner/clipboard-linux-x64-musl": "0.3.6"
46
47
  }
47
48
  }
@@ -0,0 +1,3 @@
1
+ # `@mariozechner/clipboard-linux-x64-musl`
2
+
3
+ This is the **x86_64-unknown-linux-musl** binary for `@mariozechner/clipboard`
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@mariozechner/clipboard-linux-x64-musl",
3
+ "version": "0.3.6",
4
+ "os": [
5
+ "linux"
6
+ ],
7
+ "cpu": [
8
+ "x64"
9
+ ],
10
+ "main": "clipboard.linux-x64-musl.node",
11
+ "files": [
12
+ "clipboard.linux-x64-musl.node"
13
+ ],
14
+ "license": "MIT",
15
+ "engines": {
16
+ "node": ">= 10"
17
+ },
18
+ "libc": [
19
+ "musl"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/badlogic/clipboard.git"
24
+ }
25
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entelligentsia/forgecli",
3
- "version": "0.11.2",
3
+ "version": "0.15.0",
4
4
  "description": "Forge SDLC ported onto @earendil-works/pi-coding-agent — production launcher with three bin aliases (forge/forgecli/4ge). Bundles a curated fork of pi-coding-agent vendored under earendil-works names.",
5
5
  "license": "MIT",
6
6
  "author": "Entelligentsia",
@@ -49,7 +49,7 @@
49
49
  ]
50
50
  },
51
51
  "forge": {
52
- "bundledVersion": "0.44.6",
52
+ "bundledVersion": "0.45.0",
53
53
  "forgeRoot": "../forge/forge"
54
54
  },
55
55
  "scripts": {
@@ -76,6 +76,7 @@
76
76
  "@earendil-works/pi-tui": "file:./vendor-pi/earendil-works-pi-tui-0.75.3.tgz",
77
77
  "ajv": "^8.20.0",
78
78
  "js-yaml": "^4.1.0",
79
+ "lunr": "2.3.9",
79
80
  "typebox": "^1.1.24"
80
81
  },
81
82
  "peerDependencies": {
@@ -84,6 +85,7 @@
84
85
  "devDependencies": {
85
86
  "@biomejs/biome": "^2.3.5",
86
87
  "@types/js-yaml": "^4.0.9",
88
+ "@types/lunr": "2.3.7",
87
89
  "@types/node": "^24.3.0",
88
90
  "typescript": "^5.7.3",
89
91
  "vitest": "^3.2.4"