@brawnen/agent-harness-cli 0.1.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/LICENSE +21 -0
- package/README.md +224 -0
- package/README.zh-CN.md +232 -0
- package/bin/agent-harness.js +6 -0
- package/package.json +46 -0
- package/src/commands/audit.js +110 -0
- package/src/commands/delivery.js +497 -0
- package/src/commands/docs.js +251 -0
- package/src/commands/gate.js +236 -0
- package/src/commands/init.js +711 -0
- package/src/commands/report.js +272 -0
- package/src/commands/state.js +274 -0
- package/src/commands/status.js +493 -0
- package/src/commands/task.js +316 -0
- package/src/commands/verify.js +173 -0
- package/src/index.js +101 -0
- package/src/lib/audit-store.js +80 -0
- package/src/lib/delivery-policy.js +219 -0
- package/src/lib/output-policy.js +266 -0
- package/src/lib/project-config.js +235 -0
- package/src/lib/runtime-paths.js +46 -0
- package/src/lib/state-store.js +510 -0
- package/src/lib/task-core.js +490 -0
- package/src/lib/workflow-policy.js +307 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function loadProjectConfig(cwd) {
|
|
5
|
+
const configPath = path.join(cwd, "harness.yaml");
|
|
6
|
+
if (!fs.existsSync(configPath)) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
11
|
+
const parsed = parseSimpleYaml(content);
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
allowed_paths: normalizeStringArray(parsed.allowed_paths),
|
|
15
|
+
default_commands: normalizeObject(parsed.default_commands),
|
|
16
|
+
default_mode: toNullableScalar(parsed.default_mode),
|
|
17
|
+
delivery_policy: normalizeObject(parsed.delivery_policy),
|
|
18
|
+
languages: normalizeStringArray(parsed.languages),
|
|
19
|
+
output_policy: normalizeObject(parsed.output_policy),
|
|
20
|
+
path: configPath,
|
|
21
|
+
project_name: toNullableScalar(parsed.project_name),
|
|
22
|
+
project_type: toNullableScalar(parsed.project_type),
|
|
23
|
+
protected_paths: normalizeStringArray(parsed.protected_paths),
|
|
24
|
+
risk_rules: normalizeObject(parsed.risk_rules),
|
|
25
|
+
skill_policy: normalizeObject(parsed.skill_policy),
|
|
26
|
+
task_templates: normalizeObject(parsed.task_templates),
|
|
27
|
+
workflow_policy: normalizeObject(parsed.workflow_policy),
|
|
28
|
+
version: parsed.version ?? null
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeStringArray(value) {
|
|
33
|
+
if (!Array.isArray(value)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return value
|
|
38
|
+
.map((item) => toNullableScalar(item))
|
|
39
|
+
.filter((item) => item != null)
|
|
40
|
+
.map((item) => String(item));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeObject(value) {
|
|
44
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function toNullableScalar(value) {
|
|
52
|
+
if (value == null) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseSimpleYaml(content) {
|
|
64
|
+
const lines = content.split("\n");
|
|
65
|
+
const [parsed] = parseNode(lines, findNextMeaningfulLine(lines, 0), 0);
|
|
66
|
+
return normalizeObject(parsed);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseNode(lines, startIndex, indent) {
|
|
70
|
+
const firstIndex = findNextMeaningfulLine(lines, startIndex);
|
|
71
|
+
if (firstIndex >= lines.length) {
|
|
72
|
+
return [{}, lines.length];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const firstLine = lines[firstIndex];
|
|
76
|
+
const firstIndent = countIndent(firstLine);
|
|
77
|
+
if (firstIndent < indent) {
|
|
78
|
+
return [{}, firstIndex];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (lines[firstIndex].trim().startsWith("- ")) {
|
|
82
|
+
return parseArray(lines, firstIndex, firstIndent);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return parseObject(lines, firstIndex, firstIndent);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parseObject(lines, startIndex, indent) {
|
|
89
|
+
const result = {};
|
|
90
|
+
let index = startIndex;
|
|
91
|
+
|
|
92
|
+
while (index < lines.length) {
|
|
93
|
+
index = findNextMeaningfulLine(lines, index);
|
|
94
|
+
if (index >= lines.length) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const line = lines[index];
|
|
99
|
+
const currentIndent = countIndent(line);
|
|
100
|
+
if (currentIndent < indent) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (currentIndent > indent) {
|
|
104
|
+
index += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
if (trimmed.startsWith("- ")) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
114
|
+
if (separatorIndex < 0) {
|
|
115
|
+
index += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
120
|
+
const rawValue = trimmed.slice(separatorIndex + 1).trim();
|
|
121
|
+
|
|
122
|
+
if (rawValue.length > 0) {
|
|
123
|
+
result[key] = parseScalar(rawValue);
|
|
124
|
+
index += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const nextIndex = findNextMeaningfulLine(lines, index + 1);
|
|
129
|
+
if (nextIndex >= lines.length || countIndent(lines[nextIndex]) <= currentIndent) {
|
|
130
|
+
result[key] = {};
|
|
131
|
+
index += 1;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const [child, nextCursor] = parseNode(lines, nextIndex, countIndent(lines[nextIndex]));
|
|
136
|
+
result[key] = child;
|
|
137
|
+
index = nextCursor;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return [result, index];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parseArray(lines, startIndex, indent) {
|
|
144
|
+
const result = [];
|
|
145
|
+
let index = startIndex;
|
|
146
|
+
|
|
147
|
+
while (index < lines.length) {
|
|
148
|
+
index = findNextMeaningfulLine(lines, index);
|
|
149
|
+
if (index >= lines.length) {
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const line = lines[index];
|
|
154
|
+
const currentIndent = countIndent(line);
|
|
155
|
+
if (currentIndent < indent) {
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
if (currentIndent > indent) {
|
|
159
|
+
index += 1;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const trimmed = line.trim();
|
|
164
|
+
if (!trimmed.startsWith("- ")) {
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const rawValue = trimmed.slice(2).trim();
|
|
169
|
+
if (rawValue.length > 0) {
|
|
170
|
+
result.push(parseScalar(rawValue));
|
|
171
|
+
index += 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const nextIndex = findNextMeaningfulLine(lines, index + 1);
|
|
176
|
+
if (nextIndex >= lines.length || countIndent(lines[nextIndex]) <= currentIndent) {
|
|
177
|
+
result.push(null);
|
|
178
|
+
index += 1;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const [child, nextCursor] = parseNode(lines, nextIndex, countIndent(lines[nextIndex]));
|
|
183
|
+
result.push(child);
|
|
184
|
+
index = nextCursor;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return [result, index];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function parseScalar(value) {
|
|
191
|
+
const unquoted = unquote(value);
|
|
192
|
+
if (unquoted === "true") {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
if (unquoted === "false") {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
if (unquoted === "null") {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
if (unquoted === "[]") {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
if (unquoted === "{}") {
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
if (/^-?\d+(\.\d+)?$/.test(unquoted)) {
|
|
208
|
+
return Number(unquoted);
|
|
209
|
+
}
|
|
210
|
+
return unquoted;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function unquote(value) {
|
|
214
|
+
if ((value.startsWith("\"") && value.endsWith("\"")) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
215
|
+
return value.slice(1, -1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function countIndent(line) {
|
|
222
|
+
return line.match(/^ */)?.[0].length ?? 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function findNextMeaningfulLine(lines, startIndex) {
|
|
226
|
+
let index = startIndex;
|
|
227
|
+
while (index < lines.length) {
|
|
228
|
+
const trimmed = lines[index].trim();
|
|
229
|
+
if (trimmed !== "" && !trimmed.startsWith("#")) {
|
|
230
|
+
return index;
|
|
231
|
+
}
|
|
232
|
+
index += 1;
|
|
233
|
+
}
|
|
234
|
+
return lines.length;
|
|
235
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_RUNTIME_DIR = ".harness";
|
|
5
|
+
export const LEGACY_RUNTIME_DIR = "harness";
|
|
6
|
+
|
|
7
|
+
export function resolveRuntimeDirName(cwd, options = {}) {
|
|
8
|
+
const preferExisting = options.preferExisting !== false;
|
|
9
|
+
if (preferExisting) {
|
|
10
|
+
if (fs.existsSync(path.join(cwd, DEFAULT_RUNTIME_DIR))) {
|
|
11
|
+
return DEFAULT_RUNTIME_DIR;
|
|
12
|
+
}
|
|
13
|
+
if (fs.existsSync(path.join(cwd, LEGACY_RUNTIME_DIR))) {
|
|
14
|
+
return LEGACY_RUNTIME_DIR;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return DEFAULT_RUNTIME_DIR;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function runtimePath(cwd, ...segments) {
|
|
21
|
+
return path.join(cwd, resolveRuntimeDirName(cwd), ...segments);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function defaultRuntimeRelativePath(...segments) {
|
|
25
|
+
return path.posix.join(DEFAULT_RUNTIME_DIR, ...segments);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function legacyRuntimeRelativePath(...segments) {
|
|
29
|
+
return path.posix.join(LEGACY_RUNTIME_DIR, ...segments);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function runtimeRelativePathForCwd(cwd, ...segments) {
|
|
33
|
+
const runtimeDir = resolveRuntimeDirName(cwd);
|
|
34
|
+
return path.posix.join(runtimeDir, ...segments);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function runtimeRelativeCandidates(...segments) {
|
|
38
|
+
return [
|
|
39
|
+
defaultRuntimeRelativePath(...segments),
|
|
40
|
+
legacyRuntimeRelativePath(...segments)
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function hasRuntimeSetup(cwd) {
|
|
45
|
+
return runtimeRelativeCandidates().some((relativePath) => fs.existsSync(path.join(cwd, relativePath)));
|
|
46
|
+
}
|