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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +259 -0
  3. package/README.md +261 -0
  4. package/bin/tackle.js +150 -0
  5. package/package.json +29 -0
  6. package/plugins/contracts/plugin-interface.js +244 -0
  7. package/plugins/core/hook-skill-gate/index.js +437 -0
  8. package/plugins/core/hook-skill-gate/plugin.json +12 -0
  9. package/plugins/core/provider-memory-store/index.js +403 -0
  10. package/plugins/core/provider-memory-store/plugin.json +9 -0
  11. package/plugins/core/provider-role-registry/index.js +477 -0
  12. package/plugins/core/provider-role-registry/plugin.json +9 -0
  13. package/plugins/core/provider-state-store/index.js +244 -0
  14. package/plugins/core/provider-state-store/plugin.json +9 -0
  15. package/plugins/core/skill-agent-dispatcher/plugin.json +13 -0
  16. package/plugins/core/skill-agent-dispatcher/skill.md +912 -0
  17. package/plugins/core/skill-batch-task-creator/plugin.json +13 -0
  18. package/plugins/core/skill-batch-task-creator/skill.md +616 -0
  19. package/plugins/core/skill-checklist/plugin.json +10 -0
  20. package/plugins/core/skill-checklist/skill.md +115 -0
  21. package/plugins/core/skill-completion-report/plugin.json +10 -0
  22. package/plugins/core/skill-completion-report/skill.md +331 -0
  23. package/plugins/core/skill-experience-logger/plugin.json +10 -0
  24. package/plugins/core/skill-experience-logger/skill.md +235 -0
  25. package/plugins/core/skill-human-checkpoint/plugin.json +10 -0
  26. package/plugins/core/skill-human-checkpoint/skill.md +194 -0
  27. package/plugins/core/skill-progress-tracker/plugin.json +10 -0
  28. package/plugins/core/skill-progress-tracker/skill.md +204 -0
  29. package/plugins/core/skill-role-manager/plugin.json +10 -0
  30. package/plugins/core/skill-role-manager/skill.md +252 -0
  31. package/plugins/core/skill-split-work-package/plugin.json +13 -0
  32. package/plugins/core/skill-split-work-package/skill.md +446 -0
  33. package/plugins/core/skill-task-creator/plugin.json +13 -0
  34. package/plugins/core/skill-task-creator/skill.md +744 -0
  35. package/plugins/core/skill-team-cleanup/plugin.json +10 -0
  36. package/plugins/core/skill-team-cleanup/skill.md +266 -0
  37. package/plugins/core/skill-workflow-orchestrator/plugin.json +13 -0
  38. package/plugins/core/skill-workflow-orchestrator/skill.md +274 -0
  39. package/plugins/core/validator-doc-sync/index.js +248 -0
  40. package/plugins/core/validator-doc-sync/plugin.json +9 -0
  41. package/plugins/core/validator-work-package/index.js +300 -0
  42. package/plugins/core/validator-work-package/plugin.json +9 -0
  43. package/plugins/plugin-registry.json +118 -0
  44. package/plugins/runtime/config-manager.js +306 -0
  45. package/plugins/runtime/event-bus.js +187 -0
  46. package/plugins/runtime/harness-build.js +1019 -0
  47. package/plugins/runtime/logger.js +174 -0
  48. package/plugins/runtime/plugin-loader.js +339 -0
  49. 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 };