@osovv/vv-opencode 1.3.3 → 1.3.5

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.
@@ -1,9 +1,9 @@
1
1
  // FILE: src/plugins/tool-history-compaction/transform.ts
2
- // VERSION: 0.1.0
2
+ // VERSION: 0.2.0
3
3
  // START_MODULE_CONTRACT
4
- // PURPOSE: Apply tool-history compaction to the in-memory message list the model is about to receive: walk messages newest-first, protect the recent tail, dispatch retained/read/other tools to the right compaction layer, and mutate only completed tool part outputs.
5
- // SCOPE: Completed-tool-part detection, protected-tail accounting, retention dispatch, read-slim and prune application, and idempotent in-place output rewrites.
6
- // DEPENDS: [src/plugins/tool-history-compaction/config.ts, src/plugins/tool-history-compaction/retention.ts, src/plugins/tool-history-compaction/prune.ts, src/plugins/tool-history-compaction/read-slim.ts]
4
+ // PURPOSE: Apply tool-history compaction to the in-memory message list the model is about to receive: compute an absolute recent-message window from message recency times, walk messages newest-first, dispatch retained/read/other tools to the right compaction layer, persist full pruned outputs for recoverable markers, and mutate only completed tool part outputs.
5
+ // SCOPE: Recency-time window computation, completed-tool-part detection, per-call protection accounting outside the window, retention dispatch, read-slim and prune application, optional disk-backed prune recovery, and idempotent in-place output rewrites.
6
+ // DEPENDS: [src/plugins/tool-history-compaction/config.ts, src/plugins/tool-history-compaction/retention.ts, src/plugins/tool-history-compaction/prune.ts, src/plugins/tool-history-compaction/read-slim.ts, src/plugins/tool-history-compaction/saved-output.ts]
7
7
  // LINKS: [M-PLUGIN-TOOL-HISTORY-COMPACTION]
8
8
  // ROLE: RUNTIME
9
9
  // MAP_MODE: EXPORTS
@@ -11,15 +11,17 @@
11
11
  //
12
12
  // START_MODULE_MAP
13
13
  // TransformMessage - One message entry in the transform hook output.
14
+ // recentMessageIndexes - Indices of the newest messages by recency time (array-position tie-break and fallback).
14
15
  // compactMessages - Deterministically rewrite eligible tool part outputs in place.
15
16
  // END_MODULE_MAP
16
17
  //
17
18
  // START_CHANGE_SUMMARY
18
- // LAST_CHANGE: [v0.1.0 - Established the transform core with protected tail, retention dispatch, and idempotence.]
19
+ // LAST_CHANGE: [v0.2.0 - Added recency-time recent-message window, fixed retained-tool budget leak, and added recoverable disk-backed pruning.]
19
20
  // END_CHANGE_SUMMARY
20
21
  import { pruneOutput } from "./prune.js";
21
22
  import { slimReadOutput } from "./read-slim.js";
22
23
  import { isRetainedTool } from "./retention.js";
24
+ import { savePrunedOutputOnce } from "./saved-output.js";
23
25
  function isCompletedToolPart(part) {
24
26
  return part.type === "tool" && part.state.status === "completed";
25
27
  }
@@ -30,7 +32,40 @@ function isCompactedByOpencode(part) {
30
32
  function isReadTool(tool) {
31
33
  return tool.toLowerCase() === "read";
32
34
  }
33
- // END_BLOCK_GUARDS
35
+ function messageRecencyTime(message) {
36
+ const info = message?.info;
37
+ const time = info?.time;
38
+ return time?.completed ?? time?.created ?? Number.NEGATIVE_INFINITY;
39
+ }
40
+ /**
41
+ * Compute the indices of the newest `count` messages by recency time.
42
+ * Ties are broken by array position (later index wins); messages without
43
+ * usable times fall back to array-position ordering so the newest entries are
44
+ * still selected deterministically.
45
+ * @param messages - the transform hook's message list.
46
+ * @param count - how many newest messages to protect; 0 or negative disables the window.
47
+ * @returns the set of protected message indices.
48
+ */
49
+ export function recentMessageIndexes(messages, count) {
50
+ const result = new Set();
51
+ if (count <= 0 || messages.length === 0)
52
+ return result;
53
+ const ranked = messages
54
+ .map((message, index) => ({ index, recency: messageRecencyTime(message) }))
55
+ .sort((a, b) => {
56
+ if (b.recency !== a.recency)
57
+ return b.recency - a.recency;
58
+ return b.index - a.index;
59
+ });
60
+ const take = Math.min(count, ranked.length);
61
+ for (let k = 0; k < take; k++) {
62
+ const entry = ranked[k];
63
+ if (entry)
64
+ result.add(entry.index);
65
+ }
66
+ return result;
67
+ }
68
+ // END_BLOCK_WINDOW
34
69
  // START_BLOCK_COMPACT
35
70
  /**
36
71
  * Deterministically rewrite eligible tool part outputs in place.
@@ -41,12 +76,15 @@ function isReadTool(tool) {
41
76
  * @param config - resolved compaction config.
42
77
  */
43
78
  export function compactMessages(messages, config) {
79
+ // The newest message is always protected; protectRecentMessages widens the window.
80
+ const windowSize = Math.max(1, config.protectRecentMessages);
81
+ const protectedMessages = recentMessageIndexes(messages, windowSize);
44
82
  let remainingProtection = config.protectLastCalls;
45
- let firstMessage = true;
46
83
  for (let i = messages.length - 1; i >= 0; i--) {
47
84
  const message = messages[i];
48
85
  if (!message)
49
86
  continue;
87
+ const withinWindow = protectedMessages.has(i);
50
88
  for (let j = message.parts.length - 1; j >= 0; j--) {
51
89
  const part = message.parts[j];
52
90
  if (!part || part.type !== "tool")
@@ -55,14 +93,17 @@ export function compactMessages(messages, config) {
55
93
  continue;
56
94
  if (isCompactedByOpencode(part))
57
95
  continue;
58
- const withinTail = firstMessage || remainingProtection > 0;
59
- if (withinTail) {
60
- if (!firstMessage)
61
- remainingProtection -= 1;
96
+ // Absolute recent-message window: nothing inside the newest messages is rewritten.
97
+ if (withinWindow)
62
98
  continue;
63
- }
99
+ // Retained tools are never compacted and do not consume the per-call budget.
64
100
  if (isRetainedTool(part.tool, config.retainTools))
65
101
  continue;
102
+ // Per-call protection budget applies only to compaction-eligible parts outside the window.
103
+ if (remainingProtection > 0) {
104
+ remainingProtection -= 1;
105
+ continue;
106
+ }
66
107
  const output = part.state.output;
67
108
  let rewritten;
68
109
  if (config.readSlim && isReadTool(part.tool)) {
@@ -77,16 +118,27 @@ export function compactMessages(messages, config) {
77
118
  }
78
119
  }
79
120
  else {
80
- const pruned = pruneOutput(output, config);
81
- if (pruned)
82
- rewritten = pruned.output;
121
+ const basePruned = pruneOutput(output, config);
122
+ if (basePruned) {
123
+ if (config.savePrunedOutput) {
124
+ const savedPath = savePrunedOutputOnce(output, part.callID);
125
+ if (savedPath) {
126
+ const recoverable = pruneOutput(output, config, savedPath);
127
+ rewritten = recoverable ? recoverable.output : basePruned.output;
128
+ }
129
+ else {
130
+ rewritten = basePruned.output;
131
+ }
132
+ }
133
+ else {
134
+ rewritten = basePruned.output;
135
+ }
136
+ }
83
137
  }
84
138
  if (rewritten !== undefined && rewritten !== output) {
85
139
  part.state.output = rewritten;
86
140
  }
87
141
  }
88
- if (firstMessage)
89
- firstMessage = false;
90
142
  }
91
143
  }
92
144
  // END_BLOCK_COMPACT
@@ -1 +1 @@
1
- {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/transform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform.js","sourceRoot":"","sources":["../../../src/plugins/tool-history-compaction/transform.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@osovv/vv-opencode",
3
- "version": "1.3.3",
4
- "description": "A curated, opinionated set of OpenCode plugins for spec-first, review-driven, safer agentic development.",
3
+ "version": "1.3.5",
4
+ "description": "An opinionated agentic development layer for OpenCode — spec-first when it matters, review-driven execution, portable model roles, safer tools, and long-run safety.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.3.3/schemas/vvoc/v3.json",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@1.3.5/schemas/vvoc/v3.json",
4
4
  "title": "vvoc config",
5
5
  "description": "Canonical vvoc configuration document.",
6
6
  "type": "object",
@@ -169,6 +169,8 @@
169
169
  "properties": {
170
170
  "enabled": { "type": "boolean" },
171
171
  "protectLastCalls": { "type": "integer", "minimum": 0 },
172
+ "protectRecentMessages": { "type": "integer", "minimum": 0 },
173
+ "savePrunedOutput": { "type": "boolean" },
172
174
  "minSavingsChars": { "type": "integer", "minimum": 0 },
173
175
  "outputMaxChars": { "type": "integer", "minimum": 0 },
174
176
  "headChars": { "type": "integer", "minimum": 0 },
@@ -45,27 +45,27 @@ Do not mutate files until the execution mode is explicit. In classic mode, deleg
45
45
  <purpose>List all module names</purpose>
46
46
  </helper>
47
47
  <helper name="list-tasks">
48
- <command>grep '&lt;id&gt;T-' PLAN_PATH</command>
48
+ <command>grep '&lt;TASK-T-' PLAN_PATH</command>
49
49
  <purpose>List all task IDs in document order</purpose>
50
50
  </helper>
51
51
  <helper name="extract-task">
52
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH</command>
53
- <purpose>Extract one full task by ID (replace T-NNN with actual ID like T-001)</purpose>
52
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH</command>
53
+ <purpose>Extract one full task by ID (replace T-NNN in the TASK-T-NNN element name with the actual ID, e.g. TASK-T-001)</purpose>
54
54
  </helper>
55
55
  <helper name="extract-snippet">
56
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH | sed -n '/&lt;snippet&gt;/,/&lt;\/snippet&gt;/p'</command>
56
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH | sed -n '/&lt;snippet&gt;/,/&lt;\/snippet&gt;/p'</command>
57
57
  <purpose>Extract only the code snippet for a specific task</purpose>
58
58
  </helper>
59
59
  <helper name="extract-acceptance">
60
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH | sed -n '/&lt;acceptance&gt;/,/&lt;\/acceptance&gt;/p'</command>
60
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH | sed -n '/&lt;acceptance&gt;/,/&lt;\/acceptance&gt;/p'</command>
61
61
  <purpose>Extract all acceptance criteria for a specific task</purpose>
62
62
  </helper>
63
63
  <helper name="task-file">
64
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH | grep '&lt;file&gt;'</command>
64
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH | grep '&lt;file&gt;'</command>
65
65
  <purpose>Get the target file for a specific task</purpose>
66
66
  </helper>
67
67
  <helper name="task-status">
68
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH | grep '&lt;status&gt;'</command>
68
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH | grep '&lt;status&gt;'</command>
69
69
  <purpose>Get current status of a specific task</purpose>
70
70
  </helper>
71
71
  <helper name="dependency-graph">
@@ -73,11 +73,11 @@ Do not mutate files until the execution mode is explicit. In classic mode, deleg
73
73
  <purpose>Show all task dependencies</purpose>
74
74
  </helper>
75
75
  <helper name="task-deps">
76
- <command>sed -n '/&lt;id&gt;T-NNN&lt;\/id&gt;/,/&lt;\/task&gt;/p' PLAN_PATH | grep '&lt;task_id&gt;'</command>
76
+ <command>sed -n '/&lt;TASK-T-NNN&gt;/,/&lt;\/TASK-T-NNN&gt;/p' PLAN_PATH | grep '&lt;task_id&gt;'</command>
77
77
  <purpose>List dependencies for a specific task</purpose>
78
78
  </helper>
79
79
  <helper name="count-tasks">
80
- <command>grep -c '&lt;id&gt;T-' PLAN_PATH</command>
80
+ <command>grep -c '&lt;TASK-T-' PLAN_PATH</command>
81
81
  <purpose>Count total tasks in the plan</purpose>
82
82
  </helper>
83
83
  <helper name="all-files">
@@ -103,8 +103,8 @@ Do not mutate files until the execution mode is explicit. In classic mode, deleg
103
103
  <check>Plan contains a non-empty &lt;spec&gt; path pointing to a readable active spec file at .vvoc/specs/&lt;id&gt;/spec.xml. Stop and report if the spec path is under archive/.</check>
104
104
  <check>The linked spec's top-level &lt;status&gt; is approved</check>
105
105
  <check>If the linked spec status is draft, applied, missing, or invalid, stop and report that vv-execute requires an approved active spec.</check>
106
- <check>Plan contains &lt;tasks&gt; section with at least one &lt;task&gt;</check>
107
- <check>Each task has non-empty &lt;id&gt;, &lt;title&gt;, and &lt;file&gt;</check>
106
+ <check>Plan contains &lt;tasks&gt; section with at least one &lt;TASK-T-NNN&gt; element grouped under &lt;WAVE-N&gt; elements</check>
107
+ <check>Each task element name matches the TASK-T-NNN pattern and the task has non-empty &lt;title&gt; and &lt;file&gt;. There is no child id element — identity lives in the element name.</check>
108
108
  <check>Each task has &lt;snippet&gt; (may be empty but must exist)</check>
109
109
  <check>Each task has &lt;acceptance&gt; with at least one &lt;criterion&gt;</check>
110
110
  <action>If any check fails, stop and report the issue with line numbers. Do not proceed with broken plan.</action>
@@ -36,8 +36,9 @@ You are the vv-plan skill. Your job is to take an approved spec and write an imp
36
36
  <rule>When first saving the plan, set the top-level status to &lt;status&gt;draft&lt;/status&gt;. Only change it to approved after the user explicitly reads/reviews and approves the final plan. Never set the top-level status to applied yourself; applied is reserved for vv-execute after successful execution.</rule>
37
37
  <rule>The plan contains two major sections: architecture (modules, contracts, dependencies) and tasks (implementation steps with code snippets).</rule>
38
38
  <rule>Architecture section uses child tags: module, name, purpose, file (path, role), contract, depends_on (module).</rule>
39
- <rule>Tasks use child tags: id (T-NNN pattern), title, file, status, description, depends_on (task_id), snippet (CDATA), acceptance (criterion), verification (command). Task-level &lt;status&gt; values are separate from the top-level plan lifecycle status and may remain pending until execution updates them.</rule>
40
- <rule>Every XML element is named for grep extraction. Use: `grep '&lt;id&gt;T-' plan.xml` to list tasks, `grep '&lt;criterion&gt;' plan.xml` for all criteria, `grep '&lt;task_id&gt;' plan.xml` for dependency graph.</rule>
39
+ <rule>Tasks are grouped into wave elements whose identity is the element name: &lt;WAVE-1&gt;, &lt;WAVE-2&gt;, … Each wave contains a &lt;goal&gt; and its tasks.</rule>
40
+ <rule>A task's identity is its element name in the TASK-T-NNN pattern: &lt;TASK-T-001&gt;…&lt;/TASK-T-001&gt;. The identity repeats on both boundaries so long blocks stay addressable. Tasks use child tags: title, file, status, description, depends_on (task_id), snippet (CDATA), acceptance (criterion), verification (command). Do NOT add a child id element — the element name is the single authoritative identity. Task-level &lt;status&gt; values are separate from the top-level plan lifecycle status and may remain pending until execution updates them.</rule>
41
+ <rule>Every XML element is named for grep extraction. Use: `grep '&lt;TASK-T-' plan.xml` to list tasks, `grep '&lt;criterion&gt;' plan.xml` for all criteria, `grep '&lt;task_id&gt;' plan.xml` for dependency graph.</rule>
41
42
  <rule>Populate the &lt;spec&gt; element with the path to the spec.xml this plan implements.</rule>
42
43
  <rule>If a design-context.xml was found and read as explanatory context, populate the &lt;design-context&gt; element with the path to design-context.xml so execution tools and reviewers can locate it.</rule>
43
44
  <location>Save plan.xml as a sibling of spec.xml in the same spec package directory: .vvoc/specs/&lt;id&gt;/plan.xml</location>
@@ -61,10 +62,9 @@ You are the vv-plan skill. Your job is to take an approved spec and write an imp
61
62
  </acceptance_criteria_format>
62
63
 
63
64
  <example>
64
- <rule>Here is a concrete example of one task in the new format. Every &lt;snippet&gt; uses CDATA, and every &lt;criterion&gt; is testable:</rule>
65
+ <rule>Here is a concrete example of one task. The task identity is the element name, repeated on both boundaries; every &lt;snippet&gt; uses CDATA, and every &lt;criterion&gt; is testable:</rule>
65
66
  <sample-fragment>
66
- &lt;task&gt;
67
- &lt;id&gt;T-001&lt;/id&gt;
67
+ &lt;TASK-T-001&gt;
68
68
  &lt;title&gt;LRU Cache Store&lt;/title&gt;
69
69
  &lt;file&gt;src/lib/cache-store.ts&lt;/file&gt;
70
70
  &lt;status&gt;pending&lt;/status&gt;
@@ -111,9 +111,9 @@ export type CacheStoreOptions = {
111
111
  &lt;verification&gt;
112
112
  &lt;command&gt;bun test src/lib/cache-store.test.ts&lt;/command&gt;
113
113
  &lt;/verification&gt;
114
- &lt;/task&gt;
114
+ &lt;/TASK-T-001&gt;
115
115
  </sample-fragment>
116
- <rule>Notice: the snippet uses CDATA wrapping (mandatory). Every element is a child tag (no attributes). The task has id, title, file, status, description, snippet, acceptance, and verification — all as child elements.</rule>
116
+ <rule>Notice: the task identity TASK-T-001 appears in the opening and closing element names. The snippet uses CDATA wrapping (mandatory). Every field is a child tag (no attributes): title, file, status, description, snippet, acceptance, and verification.</rule>
117
117
  </example>
118
118
 
119
119
  <file_structure>
@@ -142,6 +142,7 @@ export type CacheStoreOptions = {
142
142
  <forbidden>XML attributes in any tag — use child elements only</forbidden>
143
143
  <forbidden>Code outside CDATA — all snippets must be wrapped in CDATA sections</forbidden>
144
144
  <forbidden>Numbered criterion tags — use plain &lt;criterion&gt;, not numbered variants</forbidden>
145
+ <forbidden>Generic &lt;task&gt; or &lt;wave&gt; elements with child id/num elements — identity belongs in the element name: &lt;TASK-T-NNN&gt;, &lt;WAVE-N&gt;</forbidden>
145
146
  </no_placeholders>
146
147
 
147
148
  <self_review>
@@ -150,7 +151,7 @@ export type CacheStoreOptions = {
150
151
  <check>Acceptance criteria quality: Is every criterion testable? Could a reviewer or implementer write a failing test for it?</check>
151
152
  <check>Type consistency: Do types, signatures, and property names match across tasks? A function called `clearLayers()` in Task 3 but `clearFullLayers()` in Task 7 is a bug.</check>
152
153
  <rule>Fix issues inline as you find them. No second review pass needed — just fix and continue.</rule>
153
- <check>Format compliance: Are there zero XML attributes? Is every snippet in CDATA? Are tasks using id child tags instead of task-N numbering?</check>
154
+ <check>Format compliance: Are there zero XML attributes? Is every snippet in CDATA? Is every task a &lt;TASK-T-NNN&gt; element with identity in the element name and no child id element?</check>
154
155
  <check>Architecture presence: Does the plan have an architecture section with modules, contracts, and dependency graph?</check>
155
156
  </self_review>
156
157
 
@@ -28,11 +28,9 @@
28
28
  </architecture>
29
29
 
30
30
  <tasks>
31
- <wave>
32
- <num>1</num>
31
+ <WAVE-1>
33
32
  <goal></goal>
34
- <task>
35
- <id>T-001</id>
33
+ <TASK-T-001>
36
34
  <title></title>
37
35
  <file></file>
38
36
  <status>pending</status>
@@ -49,7 +47,7 @@
49
47
  <verification>
50
48
  <command></command>
51
49
  </verification>
52
- </task>
53
- </wave>
50
+ </TASK-T-001>
51
+ </WAVE-1>
54
52
  </tasks>
55
53
  </plan>