tackle-harness 0.0.2
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/LICENSE +21 -0
- package/README.en.md +259 -0
- package/README.md +261 -0
- package/bin/tackle.js +150 -0
- package/package.json +29 -0
- package/plugins/contracts/plugin-interface.js +244 -0
- package/plugins/core/hook-skill-gate/index.js +437 -0
- package/plugins/core/hook-skill-gate/plugin.json +12 -0
- package/plugins/core/provider-memory-store/index.js +403 -0
- package/plugins/core/provider-memory-store/plugin.json +9 -0
- package/plugins/core/provider-role-registry/index.js +477 -0
- package/plugins/core/provider-role-registry/plugin.json +9 -0
- package/plugins/core/provider-state-store/index.js +244 -0
- package/plugins/core/provider-state-store/plugin.json +9 -0
- package/plugins/core/skill-agent-dispatcher/plugin.json +13 -0
- package/plugins/core/skill-agent-dispatcher/skill.md +912 -0
- package/plugins/core/skill-batch-task-creator/plugin.json +13 -0
- package/plugins/core/skill-batch-task-creator/skill.md +616 -0
- package/plugins/core/skill-checklist/plugin.json +10 -0
- package/plugins/core/skill-checklist/skill.md +115 -0
- package/plugins/core/skill-completion-report/plugin.json +10 -0
- package/plugins/core/skill-completion-report/skill.md +331 -0
- package/plugins/core/skill-experience-logger/plugin.json +10 -0
- package/plugins/core/skill-experience-logger/skill.md +235 -0
- package/plugins/core/skill-human-checkpoint/plugin.json +10 -0
- package/plugins/core/skill-human-checkpoint/skill.md +194 -0
- package/plugins/core/skill-progress-tracker/plugin.json +10 -0
- package/plugins/core/skill-progress-tracker/skill.md +204 -0
- package/plugins/core/skill-role-manager/plugin.json +10 -0
- package/plugins/core/skill-role-manager/skill.md +252 -0
- package/plugins/core/skill-split-work-package/plugin.json +13 -0
- package/plugins/core/skill-split-work-package/skill.md +446 -0
- package/plugins/core/skill-task-creator/plugin.json +13 -0
- package/plugins/core/skill-task-creator/skill.md +744 -0
- package/plugins/core/skill-team-cleanup/plugin.json +10 -0
- package/plugins/core/skill-team-cleanup/skill.md +266 -0
- package/plugins/core/skill-workflow-orchestrator/plugin.json +13 -0
- package/plugins/core/skill-workflow-orchestrator/skill.md +274 -0
- package/plugins/core/validator-doc-sync/index.js +248 -0
- package/plugins/core/validator-doc-sync/plugin.json +9 -0
- package/plugins/core/validator-work-package/index.js +300 -0
- package/plugins/core/validator-work-package/plugin.json +9 -0
- package/plugins/plugin-registry.json +118 -0
- package/plugins/runtime/config-manager.js +306 -0
- package/plugins/runtime/event-bus.js +187 -0
- package/plugins/runtime/harness-build.js +1019 -0
- package/plugins/runtime/logger.js +174 -0
- package/plugins/runtime/plugin-loader.js +339 -0
- package/plugins/runtime/state-store.js +277 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StateStore - File-backed key-value state storage for AI Agent Harness
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - get(key) / set(key, value) / delete(key)
|
|
6
|
+
* - JSON file persistence via filesystem adapter
|
|
7
|
+
* - Auto-creates state file on first write
|
|
8
|
+
* - In-memory caching to minimize disk reads
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
var fs = require('fs');
|
|
14
|
+
var path = require('path');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Filesystem adapter for StateStore.
|
|
18
|
+
* Reads/writes a JSON file to disk.
|
|
19
|
+
*/
|
|
20
|
+
class FileSystemAdapter {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} filePath - absolute path to the state file
|
|
23
|
+
*/
|
|
24
|
+
constructor(filePath) {
|
|
25
|
+
this.filePath = filePath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Read the entire state from disk.
|
|
30
|
+
* @returns {object} parsed JSON state, or empty object if file missing/corrupt
|
|
31
|
+
*/
|
|
32
|
+
read() {
|
|
33
|
+
try {
|
|
34
|
+
var content = fs.readFileSync(this.filePath, 'utf-8');
|
|
35
|
+
return JSON.parse(content);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// File does not exist or is invalid JSON - return empty state
|
|
38
|
+
return {};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Write the entire state to disk.
|
|
44
|
+
* Creates parent directories if needed.
|
|
45
|
+
* @param {object} data - serializable state object
|
|
46
|
+
*/
|
|
47
|
+
write(data) {
|
|
48
|
+
var dir = path.dirname(this.filePath);
|
|
49
|
+
if (!fs.existsSync(dir)) {
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
fs.writeFileSync(this.filePath, JSON.stringify(data, null, 2), 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* In-memory adapter for StateStore (useful for testing).
|
|
58
|
+
*/
|
|
59
|
+
class MemoryAdapter {
|
|
60
|
+
constructor() {
|
|
61
|
+
this._data = {};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
read() {
|
|
65
|
+
return this._data;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
write(data) {
|
|
69
|
+
this._data = data;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class StateStore {
|
|
74
|
+
/**
|
|
75
|
+
* @param {object} [options]
|
|
76
|
+
* @param {string} [options.filePath] - path to state file (default: .claude-state in cwd)
|
|
77
|
+
* @param {object} [options.adapter] - custom adapter (for testing); overrides filePath
|
|
78
|
+
*/
|
|
79
|
+
constructor(options) {
|
|
80
|
+
options = options || {};
|
|
81
|
+
|
|
82
|
+
if (options.adapter) {
|
|
83
|
+
this._adapter = options.adapter;
|
|
84
|
+
} else {
|
|
85
|
+
var filePath = options.filePath || path.join(process.cwd(), '.claude-state');
|
|
86
|
+
this._adapter = new FileSystemAdapter(filePath);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** @type {object|null} cached state, null means not loaded yet */
|
|
90
|
+
this._cache = null;
|
|
91
|
+
/** @type {Map<string, Function[]>} key -> subscribers */
|
|
92
|
+
this._subscribers = new Map();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- public API ---
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a value by key.
|
|
99
|
+
* @param {string} key - dot-notation key, e.g. 'harness.state'
|
|
100
|
+
* @returns {Promise<*|undefined>}
|
|
101
|
+
*/
|
|
102
|
+
async get(key) {
|
|
103
|
+
var data = this._load();
|
|
104
|
+
return this._getNested(data, key);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Set a value by key.
|
|
109
|
+
* @param {string} key - dot-notation key
|
|
110
|
+
* @param {*} value
|
|
111
|
+
* @returns {Promise<void>}
|
|
112
|
+
*/
|
|
113
|
+
async set(key, value) {
|
|
114
|
+
var data = this._load();
|
|
115
|
+
var oldValue = this._getNested(data, key);
|
|
116
|
+
this._setNested(data, key, value);
|
|
117
|
+
this._cache = data;
|
|
118
|
+
this._adapter.write(data);
|
|
119
|
+
|
|
120
|
+
// Notify subscribers
|
|
121
|
+
var subs = this._subscribers.get(key);
|
|
122
|
+
if (subs) {
|
|
123
|
+
var self = this;
|
|
124
|
+
subs.forEach(function (cb) {
|
|
125
|
+
try {
|
|
126
|
+
cb(key, oldValue, value);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error('[StateStore] Subscriber error for key "' + key + '":', err.message);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Delete a key.
|
|
136
|
+
* @param {string} key
|
|
137
|
+
* @returns {Promise<void>}
|
|
138
|
+
*/
|
|
139
|
+
async delete(key) {
|
|
140
|
+
var data = this._load();
|
|
141
|
+
this._deleteNested(data, key);
|
|
142
|
+
this._cache = data;
|
|
143
|
+
this._adapter.write(data);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Subscribe to changes on a key.
|
|
148
|
+
* @param {string} key
|
|
149
|
+
* @param {Function} callback - callback(key, oldValue, newValue)
|
|
150
|
+
* @returns {{ unsubscribe: Function }}
|
|
151
|
+
*/
|
|
152
|
+
subscribe(key, callback) {
|
|
153
|
+
if (!this._subscribers.has(key)) {
|
|
154
|
+
this._subscribers.set(key, []);
|
|
155
|
+
}
|
|
156
|
+
this._subscribers.get(key).push(callback);
|
|
157
|
+
|
|
158
|
+
var self = this;
|
|
159
|
+
return {
|
|
160
|
+
unsubscribe: function () {
|
|
161
|
+
var subs = self._subscribers.get(key);
|
|
162
|
+
if (subs) {
|
|
163
|
+
var idx = subs.indexOf(callback);
|
|
164
|
+
if (idx !== -1) subs.splice(idx, 1);
|
|
165
|
+
if (subs.length === 0) self._subscribers.delete(key);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get all keys currently stored.
|
|
173
|
+
* @returns {Promise<string[]>}
|
|
174
|
+
*/
|
|
175
|
+
async keys() {
|
|
176
|
+
var data = this._load();
|
|
177
|
+
return this._flattenKeys(data, '');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Force reload from disk on next access.
|
|
182
|
+
*/
|
|
183
|
+
invalidate() {
|
|
184
|
+
this._cache = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// --- internal helpers ---
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Load state (with caching).
|
|
191
|
+
* @returns {object}
|
|
192
|
+
*/
|
|
193
|
+
_load() {
|
|
194
|
+
if (this._cache === null) {
|
|
195
|
+
this._cache = this._adapter.read();
|
|
196
|
+
}
|
|
197
|
+
return this._cache;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Get a nested value using dot-notation key.
|
|
202
|
+
* @param {object} obj
|
|
203
|
+
* @param {string} key - e.g. 'harness.state'
|
|
204
|
+
* @returns {*|undefined}
|
|
205
|
+
*/
|
|
206
|
+
_getNested(obj, key) {
|
|
207
|
+
var parts = key.split('.');
|
|
208
|
+
var current = obj;
|
|
209
|
+
for (var i = 0; i < parts.length; i++) {
|
|
210
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
current = current[parts[i]];
|
|
214
|
+
}
|
|
215
|
+
return current;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Set a nested value using dot-notation key.
|
|
220
|
+
* Creates intermediate objects as needed.
|
|
221
|
+
* @param {object} obj
|
|
222
|
+
* @param {string} key
|
|
223
|
+
* @param {*} value
|
|
224
|
+
*/
|
|
225
|
+
_setNested(obj, key, value) {
|
|
226
|
+
var parts = key.split('.');
|
|
227
|
+
var current = obj;
|
|
228
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
229
|
+
if (typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
230
|
+
current[parts[i]] = {};
|
|
231
|
+
}
|
|
232
|
+
current = current[parts[i]];
|
|
233
|
+
}
|
|
234
|
+
current[parts[parts.length - 1]] = value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Delete a nested value using dot-notation key.
|
|
239
|
+
* @param {object} obj
|
|
240
|
+
* @param {string} key
|
|
241
|
+
*/
|
|
242
|
+
_deleteNested(obj, key) {
|
|
243
|
+
var parts = key.split('.');
|
|
244
|
+
var current = obj;
|
|
245
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
246
|
+
if (typeof current[parts[i]] !== 'object' || current[parts[i]] === null) {
|
|
247
|
+
return; // path doesn't exist, nothing to delete
|
|
248
|
+
}
|
|
249
|
+
current = current[parts[i]];
|
|
250
|
+
}
|
|
251
|
+
delete current[parts[parts.length - 1]];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Recursively flatten an object into dot-notation keys.
|
|
256
|
+
* @param {object} obj
|
|
257
|
+
* @param {string} prefix
|
|
258
|
+
* @returns {string[]}
|
|
259
|
+
*/
|
|
260
|
+
_flattenKeys(obj, prefix) {
|
|
261
|
+
var keys = [];
|
|
262
|
+
if (typeof obj !== 'object' || obj === null) return keys;
|
|
263
|
+
|
|
264
|
+
var self = this;
|
|
265
|
+
Object.keys(obj).forEach(function (k) {
|
|
266
|
+
var fullKey = prefix ? prefix + '.' + k : k;
|
|
267
|
+
if (typeof obj[k] === 'object' && obj[k] !== null && !Array.isArray(obj[k])) {
|
|
268
|
+
keys = keys.concat(self._flattenKeys(obj[k], fullKey));
|
|
269
|
+
} else {
|
|
270
|
+
keys.push(fullKey);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
return keys;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = { StateStore, FileSystemAdapter, MemoryAdapter };
|