@osovv/vv-opencode 0.21.0 → 0.24.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.
- package/README.md +45 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -5
- package/dist/index.js.map +1 -1
- package/dist/lib/managed-agents.d.ts +3 -3
- package/dist/lib/managed-agents.js +14 -13
- package/dist/lib/managed-agents.js.map +1 -1
- package/dist/lib/model-roles.d.ts +3 -3
- package/dist/lib/model-roles.js +5 -4
- package/dist/lib/model-roles.js.map +1 -1
- package/dist/lib/opencode.js +100 -5
- package/dist/lib/opencode.js.map +1 -1
- package/dist/lib/vvoc-config.js +5 -4
- package/dist/lib/vvoc-config.js.map +1 -1
- package/dist/plugins/hashline-edit/autocorrect-replacement-lines.d.ts +1 -0
- package/dist/plugins/hashline-edit/autocorrect-replacement-lines.js +184 -0
- package/dist/plugins/hashline-edit/autocorrect-replacement-lines.js.map +1 -0
- package/dist/plugins/hashline-edit/constants.d.ts +4 -0
- package/dist/plugins/hashline-edit/constants.js +26 -0
- package/dist/plugins/hashline-edit/constants.js.map +1 -0
- package/dist/plugins/hashline-edit/edit-operation-primitives.d.ts +10 -0
- package/dist/plugins/hashline-edit/edit-operation-primitives.js +114 -0
- package/dist/plugins/hashline-edit/edit-operation-primitives.js.map +1 -0
- package/dist/plugins/hashline-edit/edit-operations.d.ts +7 -0
- package/dist/plugins/hashline-edit/edit-operations.js +168 -0
- package/dist/plugins/hashline-edit/edit-operations.js.map +1 -0
- package/dist/plugins/hashline-edit/edit-text-normalization.d.ts +6 -0
- package/dist/plugins/hashline-edit/edit-text-normalization.js +123 -0
- package/dist/plugins/hashline-edit/edit-text-normalization.js.map +1 -0
- package/dist/plugins/hashline-edit/file-text-canonicalization.d.ts +7 -0
- package/dist/plugins/hashline-edit/file-text-canonicalization.js +52 -0
- package/dist/plugins/hashline-edit/file-text-canonicalization.js.map +1 -0
- package/dist/plugins/hashline-edit/hash-computation.d.ts +3 -0
- package/dist/plugins/hashline-edit/hash-computation.js +34 -0
- package/dist/plugins/hashline-edit/hash-computation.js.map +1 -0
- package/dist/plugins/hashline-edit/index.d.ts +2 -0
- package/dist/plugins/hashline-edit/index.js +246 -0
- package/dist/plugins/hashline-edit/index.js.map +1 -0
- package/dist/plugins/hashline-edit/normalize-edits.d.ts +10 -0
- package/dist/plugins/hashline-edit/normalize-edits.js +82 -0
- package/dist/plugins/hashline-edit/normalize-edits.js.map +1 -0
- package/dist/plugins/hashline-edit/tool-description.d.ts +1 -0
- package/dist/plugins/hashline-edit/tool-description.js +35 -0
- package/dist/plugins/hashline-edit/tool-description.js.map +1 -0
- package/dist/plugins/hashline-edit/types.d.ts +17 -0
- package/dist/plugins/hashline-edit/types.js +19 -0
- package/dist/plugins/hashline-edit/types.js.map +1 -0
- package/dist/plugins/hashline-edit/validation.d.ts +20 -0
- package/dist/plugins/hashline-edit/validation.js +160 -0
- package/dist/plugins/hashline-edit/validation.js.map +1 -0
- package/dist/plugins/system-context-injection/index.js +65 -2
- package/dist/plugins/system-context-injection/index.js.map +1 -1
- package/dist/plugins/workflow/index.d.ts +2 -0
- package/dist/plugins/workflow/index.js +411 -0
- package/dist/plugins/workflow/index.js.map +1 -0
- package/dist/plugins/workflow/protocol.d.ts +33 -0
- package/dist/plugins/workflow/protocol.js +188 -0
- package/dist/plugins/workflow/protocol.js.map +1 -0
- package/dist/plugins/workflow/state.d.ts +79 -0
- package/dist/plugins/workflow/state.js +307 -0
- package/dist/plugins/workflow/state.js.map +1 -0
- package/dist/plugins/workflow/system-instruction.md +14 -0
- package/dist/plugins/workflow/tooling.d.ts +26 -0
- package/dist/plugins/workflow/tooling.js +161 -0
- package/dist/plugins/workflow/tooling.js.map +1 -0
- package/dist/plugins/workflow/transitions.d.ts +7 -0
- package/dist/plugins/workflow/transitions.js +102 -0
- package/dist/plugins/workflow/transitions.js.map +1 -0
- package/package.json +11 -3
- package/schemas/vvoc/v1.json +1 -1
- package/schemas/vvoc/v2.json +1 -1
- package/schemas/vvoc/v3.json +1 -1
- package/templates/agents/{code-reviewer.md → vv-code-reviewer.md} +12 -4
- package/templates/agents/{implementer.md → vv-implementer.md} +14 -8
- package/templates/agents/{spec-reviewer.md → vv-spec-reviewer.md} +13 -5
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// FILE: src/plugins/workflow/tooling.ts
|
|
2
|
+
// VERSION: 0.1.1
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Provide work-item tooling handlers that wrap workflow state operations with structured protocol-friendly responses.
|
|
5
|
+
// SCOPE: work_item_open, work_item_list, and work_item_close tool definitions and deterministic execution responses.
|
|
6
|
+
// DEPENDS: [src/plugins/workflow/state.ts]
|
|
7
|
+
// LINKS: [M-WORKFLOW-TOOLING]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// WorkflowToolContext - Minimal execution context required by workflow tools.
|
|
14
|
+
// WorkflowToolDefinition - Deterministic tool definition shape with execute handler.
|
|
15
|
+
// createWorkItemOpenTool - Creates work_item_open tool wrapper around openWorkItem.
|
|
16
|
+
// createWorkItemListTool - Creates work_item_list tool wrapper around listWorkItems.
|
|
17
|
+
// createWorkItemCloseTool - Creates work_item_close tool wrapper around closeWorkItem.
|
|
18
|
+
// END_MODULE_MAP
|
|
19
|
+
//
|
|
20
|
+
// START_CHANGE_SUMMARY
|
|
21
|
+
// LAST_CHANGE: [v0.1.1 - Wired work_item_list includeClosed flag into state listing options.]
|
|
22
|
+
// LAST_CHANGE: [v0.1.0 - Added workflow core tooling handlers for open/list/close operations with structured results and VVOC headers.]
|
|
23
|
+
// END_CHANGE_SUMMARY
|
|
24
|
+
import { closeWorkItem, getReviewRound, listWorkItems, openWorkItem, } from "./state.js";
|
|
25
|
+
function coerceNonEmptyString(value) {
|
|
26
|
+
if (typeof value !== "string")
|
|
27
|
+
return undefined;
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
return trimmed || undefined;
|
|
30
|
+
}
|
|
31
|
+
// START_CONTRACT: createWorkItemOpenTool
|
|
32
|
+
// PURPOSE: Build work_item_open handler that supports deterministic batch idempotent open operations.
|
|
33
|
+
// INPUTS: { store: WorkItemStore - workflow in-memory store }
|
|
34
|
+
// OUTPUTS: { WorkflowToolDefinition<OpenArgs, unknown> - executable tool definition }
|
|
35
|
+
// SIDE_EFFECTS: [Mutates in-memory work-item store through open operations]
|
|
36
|
+
// LINKS: [M-WORKFLOW-TOOLING, M-WORKFLOW-STATE]
|
|
37
|
+
// END_CONTRACT: createWorkItemOpenTool
|
|
38
|
+
export function createWorkItemOpenTool(store) {
|
|
39
|
+
return {
|
|
40
|
+
name: "work_item_open",
|
|
41
|
+
description: "Open one or more workflow work items idempotently.",
|
|
42
|
+
execute: (args, context) => {
|
|
43
|
+
const inputItems = Array.isArray(args.items) ? args.items : [];
|
|
44
|
+
const results = inputItems.map((item) => {
|
|
45
|
+
const key = coerceNonEmptyString(item.key);
|
|
46
|
+
const title = coerceNonEmptyString(item.title);
|
|
47
|
+
if (!key || !title) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
errorCode: "INVALID_INPUT",
|
|
51
|
+
message: "INVALID_INPUT: key and title must be non-empty strings",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const opened = openWorkItem(store, {
|
|
55
|
+
sessionId: context.sessionId,
|
|
56
|
+
key,
|
|
57
|
+
title,
|
|
58
|
+
});
|
|
59
|
+
if (!opened.ok) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
errorCode: opened.errorCode,
|
|
63
|
+
message: opened.message,
|
|
64
|
+
existingWorkItemId: opened.existingWorkItemId,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
ok: true,
|
|
69
|
+
reused: opened.reused,
|
|
70
|
+
workItemId: opened.record.workItemId,
|
|
71
|
+
header: opened.header,
|
|
72
|
+
state: opened.record.state,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
tool: "work_item_open",
|
|
77
|
+
sessionId: context.sessionId,
|
|
78
|
+
items: results,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// START_CONTRACT: createWorkItemListTool
|
|
84
|
+
// PURPOSE: Build work_item_list handler that returns current work items and review metadata.
|
|
85
|
+
// INPUTS: { store: WorkItemStore - workflow in-memory store }
|
|
86
|
+
// OUTPUTS: { WorkflowToolDefinition<ListArgs, unknown> - executable tool definition }
|
|
87
|
+
// SIDE_EFFECTS: [none]
|
|
88
|
+
// LINKS: [M-WORKFLOW-TOOLING, M-WORKFLOW-STATE]
|
|
89
|
+
// END_CONTRACT: createWorkItemListTool
|
|
90
|
+
export function createWorkItemListTool(store) {
|
|
91
|
+
return {
|
|
92
|
+
name: "work_item_list",
|
|
93
|
+
description: "List workflow work items for the current session.",
|
|
94
|
+
execute: (args, context) => {
|
|
95
|
+
const includeClosed = args.includeClosed === true;
|
|
96
|
+
const records = listWorkItems(store, context.sessionId, { includeClosed });
|
|
97
|
+
return {
|
|
98
|
+
tool: "work_item_list",
|
|
99
|
+
sessionId: context.sessionId,
|
|
100
|
+
includeClosed,
|
|
101
|
+
items: records.map((record) => ({
|
|
102
|
+
workItemId: record.workItemId,
|
|
103
|
+
header: `VVOC_WORK_ITEM_ID: ${record.workItemId}`,
|
|
104
|
+
key: record.key,
|
|
105
|
+
title: record.title,
|
|
106
|
+
state: record.state,
|
|
107
|
+
specReviewCount: record.specReviewCount,
|
|
108
|
+
codeReviewCount: record.codeReviewCount,
|
|
109
|
+
reviewRound: getReviewRound(record),
|
|
110
|
+
createdAt: record.createdAt,
|
|
111
|
+
updatedAt: record.updatedAt,
|
|
112
|
+
})),
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// START_CONTRACT: createWorkItemCloseTool
|
|
118
|
+
// PURPOSE: Build work_item_close handler that closes an existing work item.
|
|
119
|
+
// INPUTS: { store: WorkItemStore - workflow in-memory store }
|
|
120
|
+
// OUTPUTS: { WorkflowToolDefinition<CloseArgs, unknown> - executable tool definition }
|
|
121
|
+
// SIDE_EFFECTS: [Mutates in-memory work-item store through close operations]
|
|
122
|
+
// LINKS: [M-WORKFLOW-TOOLING, M-WORKFLOW-STATE]
|
|
123
|
+
// END_CONTRACT: createWorkItemCloseTool
|
|
124
|
+
export function createWorkItemCloseTool(store) {
|
|
125
|
+
return {
|
|
126
|
+
name: "work_item_close",
|
|
127
|
+
description: "Close a workflow work item by id.",
|
|
128
|
+
execute: (args, context) => {
|
|
129
|
+
const workItemId = coerceNonEmptyString(args.workItemId);
|
|
130
|
+
if (!workItemId) {
|
|
131
|
+
return {
|
|
132
|
+
tool: "work_item_close",
|
|
133
|
+
sessionId: context.sessionId,
|
|
134
|
+
ok: false,
|
|
135
|
+
errorCode: "INVALID_INPUT",
|
|
136
|
+
message: "INVALID_INPUT: workItemId must be a non-empty string",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const closed = closeWorkItem(store, context.sessionId, workItemId);
|
|
140
|
+
if (!closed.ok) {
|
|
141
|
+
return {
|
|
142
|
+
tool: "work_item_close",
|
|
143
|
+
sessionId: context.sessionId,
|
|
144
|
+
ok: false,
|
|
145
|
+
errorCode: closed.errorCode,
|
|
146
|
+
message: closed.message,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
tool: "work_item_close",
|
|
151
|
+
sessionId: context.sessionId,
|
|
152
|
+
ok: true,
|
|
153
|
+
workItemId: closed.record.workItemId,
|
|
154
|
+
header: closed.header,
|
|
155
|
+
state: closed.record.state,
|
|
156
|
+
closedAt: closed.record.closedAt,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=tooling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooling.js","sourceRoot":"","sources":["../../../src/plugins/workflow/tooling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ParsedResultBlock, TrackedAgentName } from "./protocol.js";
|
|
2
|
+
import type { WorkItemState } from "./state.js";
|
|
3
|
+
export declare const MAX_REVIEW_ROUNDS = 2;
|
|
4
|
+
export declare function getAllowedNextAgent(state: WorkItemState): TrackedAgentName | null;
|
|
5
|
+
export declare function isAllowedTransition(state: WorkItemState, agent: TrackedAgentName): boolean;
|
|
6
|
+
export declare function getNextState(currentState: WorkItemState, result: ParsedResultBlock): WorkItemState;
|
|
7
|
+
export declare function shouldBlockRound(reviewRound: number): boolean;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// FILE: src/plugins/workflow/transitions.ts
|
|
2
|
+
// VERSION: 0.1.0
|
|
3
|
+
// START_MODULE_CONTRACT
|
|
4
|
+
// PURPOSE: Provide deterministic workflow transition rules, allowed-next-agent resolution, and loop-gate policy checks.
|
|
5
|
+
// SCOPE: Work-item state transition mapping from tracked results, launch permission checks by state, and review-round limit checks.
|
|
6
|
+
// DEPENDS: [src/plugins/workflow/protocol.ts, src/plugins/workflow/state.ts]
|
|
7
|
+
// LINKS: [M-WORKFLOW-TRANSITIONS]
|
|
8
|
+
// ROLE: RUNTIME
|
|
9
|
+
// MAP_MODE: EXPORTS
|
|
10
|
+
// END_MODULE_CONTRACT
|
|
11
|
+
//
|
|
12
|
+
// START_MODULE_MAP
|
|
13
|
+
// MAX_REVIEW_ROUNDS - Maximum allowed review rounds before loop-gate block.
|
|
14
|
+
// getNextState - Computes next work-item state from tracked agent result.
|
|
15
|
+
// getAllowedNextAgent - Resolves the only allowed tracked agent for a state.
|
|
16
|
+
// isAllowedTransition - Checks whether launching an agent from current state is allowed.
|
|
17
|
+
// shouldBlockRound - Enforces loop-gate policy for review rounds.
|
|
18
|
+
// END_MODULE_MAP
|
|
19
|
+
//
|
|
20
|
+
// START_CHANGE_SUMMARY
|
|
21
|
+
// LAST_CHANGE: [v0.1.0 - Added deterministic state transition policy, launch-allowance resolution, and review round-gate checks.]
|
|
22
|
+
// END_CHANGE_SUMMARY
|
|
23
|
+
export const MAX_REVIEW_ROUNDS = 2;
|
|
24
|
+
// START_CONTRACT: getAllowedNextAgent
|
|
25
|
+
// PURPOSE: Resolve the single tracked agent allowed to run from the current state.
|
|
26
|
+
// INPUTS: { state: WorkItemState - current work-item lifecycle state }
|
|
27
|
+
// OUTPUTS: { TrackedAgentName | null - allowed tracked agent or null when no launch is allowed }
|
|
28
|
+
// SIDE_EFFECTS: [none]
|
|
29
|
+
// LINKS: [M-WORKFLOW-TRANSITIONS]
|
|
30
|
+
// END_CONTRACT: getAllowedNextAgent
|
|
31
|
+
export function getAllowedNextAgent(state) {
|
|
32
|
+
if (state === "open" || state === "awaiting_implementer") {
|
|
33
|
+
return "vv-implementer";
|
|
34
|
+
}
|
|
35
|
+
if (state === "awaiting_spec_review") {
|
|
36
|
+
return "vv-spec-reviewer";
|
|
37
|
+
}
|
|
38
|
+
if (state === "awaiting_code_review") {
|
|
39
|
+
return "vv-code-reviewer";
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// START_CONTRACT: isAllowedTransition
|
|
44
|
+
// PURPOSE: Check whether launching the requested tracked agent is valid for current state.
|
|
45
|
+
// INPUTS: { state: WorkItemState - current state, agent: TrackedAgentName - requested tracked subagent }
|
|
46
|
+
// OUTPUTS: { boolean - true when launch is allowed }
|
|
47
|
+
// SIDE_EFFECTS: [none]
|
|
48
|
+
// LINKS: [M-WORKFLOW-TRANSITIONS]
|
|
49
|
+
// END_CONTRACT: isAllowedTransition
|
|
50
|
+
export function isAllowedTransition(state, agent) {
|
|
51
|
+
return getAllowedNextAgent(state) === agent;
|
|
52
|
+
}
|
|
53
|
+
// START_CONTRACT: getNextState
|
|
54
|
+
// PURPOSE: Compute deterministic next work-item state from a tracked subagent result.
|
|
55
|
+
// INPUTS: { currentState: WorkItemState - current lifecycle state, result: ParsedResultBlock - validated tracked result block }
|
|
56
|
+
// OUTPUTS: { WorkItemState - next lifecycle state }
|
|
57
|
+
// SIDE_EFFECTS: [none]
|
|
58
|
+
// LINKS: [M-WORKFLOW-TRANSITIONS]
|
|
59
|
+
// END_CONTRACT: getNextState
|
|
60
|
+
export function getNextState(currentState, result) {
|
|
61
|
+
if (result.agent === "vv-implementer") {
|
|
62
|
+
if (result.status === "DONE" || result.status === "DONE_WITH_CONCERNS") {
|
|
63
|
+
return "awaiting_spec_review";
|
|
64
|
+
}
|
|
65
|
+
if (result.status === "NEEDS_CONTEXT") {
|
|
66
|
+
return "needs_context";
|
|
67
|
+
}
|
|
68
|
+
return "blocked";
|
|
69
|
+
}
|
|
70
|
+
if (result.agent === "vv-spec-reviewer") {
|
|
71
|
+
if (result.status === "PASS") {
|
|
72
|
+
return "awaiting_code_review";
|
|
73
|
+
}
|
|
74
|
+
if (result.status === "FAIL") {
|
|
75
|
+
return "awaiting_implementer";
|
|
76
|
+
}
|
|
77
|
+
return "needs_context";
|
|
78
|
+
}
|
|
79
|
+
if (result.agent === "vv-code-reviewer") {
|
|
80
|
+
if (result.status === "PASS") {
|
|
81
|
+
return "ready_to_close";
|
|
82
|
+
}
|
|
83
|
+
if (result.status === "FAIL") {
|
|
84
|
+
return "awaiting_implementer";
|
|
85
|
+
}
|
|
86
|
+
return "needs_context";
|
|
87
|
+
}
|
|
88
|
+
return currentState;
|
|
89
|
+
}
|
|
90
|
+
// START_CONTRACT: shouldBlockRound
|
|
91
|
+
// PURPOSE: Enforce loop-gate policy by rejecting rounds above MAX_REVIEW_ROUNDS.
|
|
92
|
+
// INPUTS: { reviewRound: number - computed review round }
|
|
93
|
+
// OUTPUTS: { boolean - true when launch must be blocked }
|
|
94
|
+
// SIDE_EFFECTS: [none]
|
|
95
|
+
// LINKS: [M-WORKFLOW-TRANSITIONS]
|
|
96
|
+
// END_CONTRACT: shouldBlockRound
|
|
97
|
+
// START_BLOCK_CHECK_ROUND_LIMIT
|
|
98
|
+
export function shouldBlockRound(reviewRound) {
|
|
99
|
+
return reviewRound > MAX_REVIEW_ROUNDS;
|
|
100
|
+
}
|
|
101
|
+
// END_BLOCK_CHECK_ROUND_LIMIT
|
|
102
|
+
//# sourceMappingURL=transitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transitions.js","sourceRoot":"","sources":["../../../src/plugins/workflow/transitions.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@osovv/vv-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Portable OpenCode workflow plugins, explicit memory, and CLI tooling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,6 +38,10 @@
|
|
|
38
38
|
"types": "./dist/plugins/guardian/index.d.ts",
|
|
39
39
|
"import": "./dist/plugins/guardian/index.js"
|
|
40
40
|
},
|
|
41
|
+
"./plugins/hashline-edit": {
|
|
42
|
+
"types": "./dist/plugins/hashline-edit/index.d.ts",
|
|
43
|
+
"import": "./dist/plugins/hashline-edit/index.js"
|
|
44
|
+
},
|
|
41
45
|
"./plugins/memory": {
|
|
42
46
|
"types": "./dist/plugins/memory/index.d.ts",
|
|
43
47
|
"import": "./dist/plugins/memory/index.js"
|
|
@@ -50,20 +54,24 @@
|
|
|
50
54
|
"types": "./dist/plugins/system-context-injection/index.d.ts",
|
|
51
55
|
"import": "./dist/plugins/system-context-injection/index.js"
|
|
52
56
|
},
|
|
57
|
+
"./plugins/workflow": {
|
|
58
|
+
"types": "./dist/plugins/workflow/index.d.ts",
|
|
59
|
+
"import": "./dist/plugins/workflow/index.js"
|
|
60
|
+
},
|
|
53
61
|
"./plugins/secrets-redaction": {
|
|
54
62
|
"types": "./dist/plugins/secrets-redaction/index.d.ts",
|
|
55
63
|
"import": "./dist/plugins/secrets-redaction/index.js"
|
|
56
64
|
}
|
|
57
65
|
},
|
|
58
66
|
"scripts": {
|
|
59
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/plugins/memory && cp src/plugins/memory/system-instruction.md dist/plugins/memory/system-instruction.md",
|
|
67
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/plugins/memory dist/plugins/workflow && cp src/plugins/memory/system-instruction.md dist/plugins/memory/system-instruction.md && cp src/plugins/workflow/system-instruction.md dist/plugins/workflow/system-instruction.md",
|
|
60
68
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
61
69
|
"lint": "oxlint --deny warnings src",
|
|
62
70
|
"fmt": "oxfmt --write src",
|
|
63
71
|
"fmt:check": "oxfmt --check src",
|
|
64
72
|
"test": "bun test",
|
|
65
73
|
"check": "bun run typecheck && bun run lint && bun run fmt:check && bun test",
|
|
66
|
-
"pack:check": "bun run build && bun -e \"const root = await import('./dist/index.js'); if (!('GuardianPlugin' in root)) throw new Error('dist root export missing GuardianPlugin'); if (!('MemoryPlugin' in root)) throw new Error('dist root export missing MemoryPlugin'); if (!('ModelRolesPlugin' in root)) throw new Error('dist root export missing ModelRolesPlugin'); if (!('SystemContextInjectionPlugin' in root)) throw new Error('dist root export missing SystemContextInjectionPlugin'); if (!('SecretsRedactionPlugin' in root)) throw new Error('dist root export missing SecretsRedactionPlugin'); await import('./dist/plugins/guardian/index.js'); await import('./dist/plugins/memory/index.js'); await import('./dist/plugins/model-roles/index.js'); await import('./dist/plugins/system-context-injection/index.js')\" && npm pack --dry-run",
|
|
74
|
+
"pack:check": "bun run build && bun -e \"const root = await import('./dist/index.js'); if (!('GuardianPlugin' in root)) throw new Error('dist root export missing GuardianPlugin'); if (!('HashlineEditPlugin' in root)) throw new Error('dist root export missing HashlineEditPlugin'); if (!('MemoryPlugin' in root)) throw new Error('dist root export missing MemoryPlugin'); if (!('ModelRolesPlugin' in root)) throw new Error('dist root export missing ModelRolesPlugin'); if (!('SystemContextInjectionPlugin' in root)) throw new Error('dist root export missing SystemContextInjectionPlugin'); if (!('WorkflowPlugin' in root)) throw new Error('dist root export missing WorkflowPlugin'); if (!('SecretsRedactionPlugin' in root)) throw new Error('dist root export missing SecretsRedactionPlugin'); await import('./dist/plugins/guardian/index.js'); await import('./dist/plugins/hashline-edit/index.js'); await import('./dist/plugins/memory/index.js'); await import('./dist/plugins/model-roles/index.js'); await import('./dist/plugins/system-context-injection/index.js'); await import('./dist/plugins/workflow/index.js')\" && npm pack --dry-run",
|
|
67
75
|
"prepare": "lefthook install --force"
|
|
68
76
|
},
|
|
69
77
|
"dependencies": {
|
package/schemas/vvoc/v1.json
CHANGED
|
@@ -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@0.
|
|
3
|
+
"$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@0.24.0/schemas/vvoc/v1.json",
|
|
4
4
|
"title": "vvoc config",
|
|
5
5
|
"description": "Canonical vvoc configuration document.",
|
|
6
6
|
"type": "object",
|
package/schemas/vvoc/v2.json
CHANGED
|
@@ -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@0.
|
|
3
|
+
"$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@0.24.0/schemas/vvoc/v2.json",
|
|
4
4
|
"title": "vvoc config",
|
|
5
5
|
"description": "Canonical vvoc configuration document.",
|
|
6
6
|
"type": "object",
|
package/schemas/vvoc/v3.json
CHANGED
|
@@ -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@0.
|
|
3
|
+
"$id": "https://cdn.jsdelivr.net/npm/@osovv/vv-opencode@0.24.0/schemas/vvoc/v3.json",
|
|
4
4
|
"title": "vvoc config",
|
|
5
5
|
"description": "Canonical vvoc configuration document.",
|
|
6
6
|
"type": "object",
|
|
@@ -5,7 +5,7 @@ permission:
|
|
|
5
5
|
edit: deny
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are the code-reviewer subagent.
|
|
8
|
+
You are the vv-code-reviewer subagent.
|
|
9
9
|
|
|
10
10
|
Review the actual code with a practical senior-engineering mindset.
|
|
11
11
|
Do not make code changes.
|
|
@@ -37,13 +37,21 @@ Rules:
|
|
|
37
37
|
- If a concern lacks a concrete failure mode, keep it under residual risks instead of calling it a finding.
|
|
38
38
|
- If no issues are found, say `No findings` explicitly and mention any residual risk or testing gap.
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
Final response protocol:
|
|
41
|
+
|
|
42
|
+
- Start with this top block in this exact key order:
|
|
43
|
+
- `VVOC_WORK_ITEM_ID: wi-1`
|
|
44
|
+
- `VVOC_STATUS: PASS`
|
|
45
|
+
- Replace values as needed using only allowed values.
|
|
46
|
+
- `Status: PASS | FAIL | NEEDS_CONTEXT`
|
|
47
|
+
- Allowed statuses: `PASS | FAIL | NEEDS_CONTEXT`
|
|
48
|
+
|
|
49
|
+
Output format after the top block:
|
|
41
50
|
|
|
42
|
-
- Status: PASS | FAIL
|
|
43
51
|
- Critical
|
|
44
52
|
- Important
|
|
45
53
|
- Minor
|
|
46
54
|
- Residual risks / testing gaps
|
|
47
55
|
- Brief assessment
|
|
48
56
|
|
|
49
|
-
If no issues are found,
|
|
57
|
+
If no issues are found, keep `VVOC_STATUS: PASS` and use `- none` under Critical, Important, and Minor.
|
|
@@ -3,7 +3,7 @@ description: Implements approved changes with focused verification and a minimal
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are the implementer subagent.
|
|
6
|
+
You are the vv-implementer subagent.
|
|
7
7
|
|
|
8
8
|
Your job is to execute the assigned task exactly, with the smallest correct change and fresh verification evidence.
|
|
9
9
|
|
|
@@ -58,14 +58,20 @@ Before reporting back, self-review your work:
|
|
|
58
58
|
|
|
59
59
|
If you find issues during self-review, fix them before reporting.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Final response protocol:
|
|
62
62
|
|
|
63
|
-
-
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
63
|
+
- Start with this top block in this exact key order:
|
|
64
|
+
- `VVOC_WORK_ITEM_ID: wi-1`
|
|
65
|
+
- `VVOC_STATUS: DONE`
|
|
66
|
+
- `VVOC_ROUTE: change_with_review`
|
|
67
|
+
- Replace values as needed using only allowed values.
|
|
68
|
+
- `Status: DONE | DONE_WITH_CONCERNS | NEEDS_CONTEXT | BLOCKED`
|
|
69
|
+
- Allowed statuses: `DONE | DONE_WITH_CONCERNS | NEEDS_CONTEXT | BLOCKED`
|
|
70
|
+
- Then provide:
|
|
71
|
+
- `Changed: ...`
|
|
72
|
+
- `Verified: ...`
|
|
73
|
+
- `Assumptions: ...`
|
|
74
|
+
- `Concerns: ...`
|
|
69
75
|
|
|
70
76
|
Use DONE_WITH_CONCERNS when the task is complete but you still have a material concern.
|
|
71
77
|
Use NEEDS_CONTEXT when safe completion depends on information that was not provided.
|
|
@@ -5,7 +5,7 @@ permission:
|
|
|
5
5
|
edit: deny
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
You are the spec-reviewer subagent.
|
|
8
|
+
You are the vv-spec-reviewer subagent.
|
|
9
9
|
|
|
10
10
|
Your job is to verify whether the implementation matches the requested behavior. Nothing more, nothing less.
|
|
11
11
|
Do not make code changes.
|
|
@@ -42,13 +42,21 @@ Method:
|
|
|
42
42
|
- Do not fail purely for route or process choices unless they caused a concrete spec mismatch.
|
|
43
43
|
- If the request is too incomplete to score safely, return `NEEDS_CONTEXT` instead of guessing.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
Final response protocol:
|
|
46
46
|
|
|
47
|
-
-
|
|
47
|
+
- Start with this top block in this exact key order:
|
|
48
|
+
- `VVOC_WORK_ITEM_ID: wi-1`
|
|
49
|
+
- `VVOC_STATUS: PASS`
|
|
50
|
+
- Replace values as needed using only allowed values.
|
|
48
51
|
- `Status: PASS | FAIL | NEEDS_CONTEXT`
|
|
52
|
+
- Allowed statuses: `PASS | FAIL | NEEDS_CONTEXT`
|
|
53
|
+
|
|
54
|
+
Output:
|
|
55
|
+
|
|
56
|
+
- Use this exact structure after the top block:
|
|
49
57
|
- `Findings:`
|
|
50
58
|
- `- [Missing|Extra|Wrong|Unproven] path:line - explanation`
|
|
51
59
|
- `Residual uncertainty:`
|
|
52
|
-
- If compliant,
|
|
60
|
+
- If compliant, set `Findings:` to `- none`.
|
|
53
61
|
- If not compliant, list findings first with file references and label each one as Missing, Extra, Wrong, or Unproven.
|
|
54
|
-
- If the request itself is unstable or incomplete, use `
|
|
62
|
+
- If the request itself is unstable or incomplete, use `VVOC_STATUS: NEEDS_CONTEXT` and explain what prevents a safe pass/fail judgment.
|