@h-rig/runtime 0.0.6-alpha.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 +27 -0
- package/dist/bin/rig-agent-dispatch.js +9615 -0
- package/dist/bin/rig-agent.js +9512 -0
- package/dist/bin/rig-browser-tool.js +269 -0
- package/dist/src/agent-mode.js +48 -0
- package/dist/src/baked-secrets.js +121 -0
- package/dist/src/binary-build-worker.js +312 -0
- package/dist/src/binary-run.js +540 -0
- package/dist/src/boundaries.js +1 -0
- package/dist/src/build-time-config.js +25 -0
- package/dist/src/control-plane/agent-roles.js +27 -0
- package/dist/src/control-plane/agent-wrapper.js +9621 -0
- package/dist/src/control-plane/authority-files.js +582 -0
- package/dist/src/control-plane/browser-contract.js +135 -0
- package/dist/src/control-plane/controlled-bash.js +1111 -0
- package/dist/src/control-plane/errors.js +13 -0
- package/dist/src/control-plane/harness-main.js +10828 -0
- package/dist/src/control-plane/hook-materializer.js +75 -0
- package/dist/src/control-plane/hooks/audit-trail.js +353 -0
- package/dist/src/control-plane/hooks/completion-verification.js +7552 -0
- package/dist/src/control-plane/hooks/import-guard.js +890 -0
- package/dist/src/control-plane/hooks/inject-context.js +4189 -0
- package/dist/src/control-plane/hooks/post-edit-lint.js +43 -0
- package/dist/src/control-plane/hooks/safety-guard.js +910 -0
- package/dist/src/control-plane/hooks/scope-guard.js +907 -0
- package/dist/src/control-plane/hooks/shared.js +44 -0
- package/dist/src/control-plane/hooks/submodule-branch.js +7797 -0
- package/dist/src/control-plane/hooks/task-runtime-start.js +7799 -0
- package/dist/src/control-plane/hooks/test-integrity-guard.js +891 -0
- package/dist/src/control-plane/materialize-task-config.js +453 -0
- package/dist/src/control-plane/memory-sync/cli.js +2019 -0
- package/dist/src/control-plane/memory-sync/db.js +753 -0
- package/dist/src/control-plane/memory-sync/embed.js +281 -0
- package/dist/src/control-plane/memory-sync/index.js +2049 -0
- package/dist/src/control-plane/memory-sync/query.js +294 -0
- package/dist/src/control-plane/memory-sync/read.js +784 -0
- package/dist/src/control-plane/memory-sync/types.js +6 -0
- package/dist/src/control-plane/memory-sync/write.js +1547 -0
- package/dist/src/control-plane/native/git-native.js +490 -0
- package/dist/src/control-plane/native/git-ops.js +2860 -0
- package/dist/src/control-plane/native/harness-cli.js +9721 -0
- package/dist/src/control-plane/native/pr-automation.js +373 -0
- package/dist/src/control-plane/native/profile-ops.js +481 -0
- package/dist/src/control-plane/native/repo-ops.js +2342 -0
- package/dist/src/control-plane/native/root-resolver.js +66 -0
- package/dist/src/control-plane/native/run-ops.js +3281 -0
- package/dist/src/control-plane/native/runtime-native-sidecar.js +299 -0
- package/dist/src/control-plane/native/runtime-native.js +392 -0
- package/dist/src/control-plane/native/scope-rules.js +17 -0
- package/dist/src/control-plane/native/task-ops.js +6320 -0
- package/dist/src/control-plane/native/task-state.js +1512 -0
- package/dist/src/control-plane/native/utils.js +535 -0
- package/dist/src/control-plane/native/validator-binaries.js +889 -0
- package/dist/src/control-plane/native/validator.js +2197 -0
- package/dist/src/control-plane/native/verifier.js +3249 -0
- package/dist/src/control-plane/native/workspace-ops.js +1635 -0
- package/dist/src/control-plane/plugin-host-context.js +334 -0
- package/dist/src/control-plane/project-main-pre-run-sync.js +630 -0
- package/dist/src/control-plane/provider/claude-stream-records.js +158 -0
- package/dist/src/control-plane/provider/codex-app-server.js +885 -0
- package/dist/src/control-plane/provider/codex-exec-records.js +203 -0
- package/dist/src/control-plane/provider/rig-task-run-skill.js +39 -0
- package/dist/src/control-plane/provider/runtime-instructions.js +96 -0
- package/dist/src/control-plane/remote.js +854 -0
- package/dist/src/control-plane/repos/index.js +473 -0
- package/dist/src/control-plane/repos/layout.js +124 -0
- package/dist/src/control-plane/repos/mirror/bootstrap.js +268 -0
- package/dist/src/control-plane/repos/mirror/refresh.js +398 -0
- package/dist/src/control-plane/repos/mirror/state.js +167 -0
- package/dist/src/control-plane/repos/registry.js +77 -0
- package/dist/src/control-plane/repos/types.js +1 -0
- package/dist/src/control-plane/runtime/agent-mode.js +48 -0
- package/dist/src/control-plane/runtime/baked-secrets.js +120 -0
- package/dist/src/control-plane/runtime/claude-tool-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/claude-tool-router.js +520 -0
- package/dist/src/control-plane/runtime/context.js +216 -0
- package/dist/src/control-plane/runtime/events.js +218 -0
- package/dist/src/control-plane/runtime/guard-types.js +6 -0
- package/dist/src/control-plane/runtime/guard.js +880 -0
- package/dist/src/control-plane/runtime/image/fingerprint-sidecar.js +1194 -0
- package/dist/src/control-plane/runtime/image/index.js +2255 -0
- package/dist/src/control-plane/runtime/image-fingerprint-sidecar.js +1191 -0
- package/dist/src/control-plane/runtime/image.js +2255 -0
- package/dist/src/control-plane/runtime/index.js +8511 -0
- package/dist/src/control-plane/runtime/isolation/discovery.js +599 -0
- package/dist/src/control-plane/runtime/isolation/home.js +1217 -0
- package/dist/src/control-plane/runtime/isolation/index.js +8193 -0
- package/dist/src/control-plane/runtime/isolation/runner.js +2651 -0
- package/dist/src/control-plane/runtime/isolation/shared.js +501 -0
- package/dist/src/control-plane/runtime/isolation/toolchain.js +1892 -0
- package/dist/src/control-plane/runtime/isolation/types.js +1 -0
- package/dist/src/control-plane/runtime/isolation/worktree.js +509 -0
- package/dist/src/control-plane/runtime/isolation.js +8193 -0
- package/dist/src/control-plane/runtime/overlay.js +67 -0
- package/dist/src/control-plane/runtime/plugin-mode.js +41 -0
- package/dist/src/control-plane/runtime/plugins.js +1131 -0
- package/dist/src/control-plane/runtime/provisioning-env.js +220 -0
- package/dist/src/control-plane/runtime/queue.js +8358 -0
- package/dist/src/control-plane/runtime/rig-shell.js +205 -0
- package/dist/src/control-plane/runtime/rig-tools.js +182 -0
- package/dist/src/control-plane/runtime/runner-context.js +1 -0
- package/dist/src/control-plane/runtime/runtime-paths.js +184 -0
- package/dist/src/control-plane/runtime/sandbox/backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox/backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox/backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox/backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox/orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox/utils.js +137 -0
- package/dist/src/control-plane/runtime/sandbox-backend-bwrap.js +311 -0
- package/dist/src/control-plane/runtime/sandbox-backend-none.js +21 -0
- package/dist/src/control-plane/runtime/sandbox-backend-seatbelt.js +268 -0
- package/dist/src/control-plane/runtime/sandbox-backend.js +1718 -0
- package/dist/src/control-plane/runtime/sandbox-orchestrator.js +1745 -0
- package/dist/src/control-plane/runtime/sandbox-utils.js +137 -0
- package/dist/src/control-plane/runtime/snapshot/index.js +454 -0
- package/dist/src/control-plane/runtime/snapshot/sidecar.js +502 -0
- package/dist/src/control-plane/runtime/snapshot/task-run.js +1578 -0
- package/dist/src/control-plane/runtime/snapshot-sidecar.js +498 -0
- package/dist/src/control-plane/runtime/snapshot.js +454 -0
- package/dist/src/control-plane/runtime/task-run-snapshot.js +1578 -0
- package/dist/src/control-plane/runtime/tool-gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/browser-tools.js +32 -0
- package/dist/src/control-plane/runtime/tooling/claude-router-binary.js +343 -0
- package/dist/src/control-plane/runtime/tooling/claude-router.js +524 -0
- package/dist/src/control-plane/runtime/tooling/file-tools.js +182 -0
- package/dist/src/control-plane/runtime/tooling/gateway.js +422 -0
- package/dist/src/control-plane/runtime/tooling/index.js +1290 -0
- package/dist/src/control-plane/runtime/tooling/shell.js +205 -0
- package/dist/src/control-plane/runtime/types.js +1 -0
- package/dist/src/control-plane/setup-version.js +14 -0
- package/dist/src/control-plane/state-sync/index.js +1509 -0
- package/dist/src/control-plane/state-sync/read.js +856 -0
- package/dist/src/control-plane/state-sync/reconcile.js +260 -0
- package/dist/src/control-plane/state-sync/repo.js +302 -0
- package/dist/src/control-plane/state-sync/types.js +111 -0
- package/dist/src/control-plane/state-sync/write.js +1469 -0
- package/dist/src/control-plane/task-fields.js +38 -0
- package/dist/src/control-plane/task-source-bootstrap.js +46 -0
- package/dist/src/control-plane/task-source.js +30 -0
- package/dist/src/control-plane/tasks/legacy-task-config-source.js +130 -0
- package/dist/src/control-plane/tasks/plugin-task-source.js +103 -0
- package/dist/src/control-plane/tasks/source-aware-task-config-source.js +611 -0
- package/dist/src/control-plane/tasks/source-lifecycle.js +1093 -0
- package/dist/src/control-plane/tasks/task-record-reader.js +9 -0
- package/dist/src/control-plane/validators/boundary/public-apis.js +107 -0
- package/dist/src/control-plane/validators/integration/_shared.js +51 -0
- package/dist/src/control-plane/validators/integration/adm-audit-http.js +85 -0
- package/dist/src/control-plane/validators/integration/adm-auth-http.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-issuer-http.js +80 -0
- package/dist/src/control-plane/validators/integration/adm-migration.js +78 -0
- package/dist/src/control-plane/validators/integration/adm-scaffold.js +78 -0
- package/dist/src/control-plane/validators/runtime-registration.js +64 -0
- package/dist/src/control-plane/validators/shared.js +683 -0
- package/dist/src/events.js +218 -0
- package/dist/src/execution.js +35 -0
- package/dist/src/index.js +1633 -0
- package/dist/src/layout.js +145 -0
- package/dist/src/local-server.js +202 -0
- package/dist/src/plugins.js +329 -0
- package/dist/src/remote-http.js +83 -0
- package/dist/src/runtime-context.js +216 -0
- package/dist/src/types.js +1 -0
- package/native/darwin-arm64/bin/rig-git +0 -0
- package/native/darwin-arm64/bin/rig-shell +0 -0
- package/native/darwin-arm64/bin/rig-tools +0 -0
- package/native/darwin-arm64/lib/runtime-native-darwin-arm64.dylib +0 -0
- package/native/darwin-arm64/lib/runtime-native.dylib +0 -0
- package/native/darwin-arm64/manifest.json +1 -0
- package/native/linux-x64/bin/rig-git +0 -0
- package/native/linux-x64/bin/rig-shell +0 -0
- package/native/linux-x64/bin/rig-tools +0 -0
- package/native/linux-x64/lib/runtime-native-linux-x64.so +0 -0
- package/native/linux-x64/lib/runtime-native.so +0 -0
- package/native/linux-x64/manifest.json +1 -0
- package/package.json +74 -0
- package/skills/rig-task-run.md +71 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
3
|
+
import { spawnSync } from "child_process";
|
|
4
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "fs";
|
|
5
|
+
import { basename, join, resolve as resolve2 } from "path";
|
|
6
|
+
|
|
7
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
8
|
+
import { existsSync, readFileSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
|
|
11
|
+
// packages/runtime/src/control-plane/tasks/task-record-reader.ts
|
|
12
|
+
async function findTaskById(reader, id) {
|
|
13
|
+
const tasks = await reader.listTasks();
|
|
14
|
+
return tasks.find((task) => task.id === id) ?? null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// packages/runtime/src/control-plane/tasks/legacy-task-config-source.ts
|
|
18
|
+
class LegacyTaskConfigReadError extends Error {
|
|
19
|
+
code = "LEGACY_TASK_CONFIG_READ_FAILED";
|
|
20
|
+
projectRoot;
|
|
21
|
+
configPath;
|
|
22
|
+
cause;
|
|
23
|
+
constructor(input) {
|
|
24
|
+
super(input.message, { cause: input.cause });
|
|
25
|
+
this.name = "LegacyTaskConfigReadError";
|
|
26
|
+
this.projectRoot = input.projectRoot;
|
|
27
|
+
this.configPath = input.configPath;
|
|
28
|
+
this.cause = input.cause;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function createLegacyTaskConfigRecordReader(projectRoot, options = {}) {
|
|
32
|
+
const configPath = options.configPath ?? resolve(projectRoot, ".rig", "task-config.json");
|
|
33
|
+
const reader = {
|
|
34
|
+
async listTasks() {
|
|
35
|
+
return readLegacyTaskRecords(projectRoot, configPath);
|
|
36
|
+
},
|
|
37
|
+
async getTask(id) {
|
|
38
|
+
return findTaskById(reader, id);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
return reader;
|
|
42
|
+
}
|
|
43
|
+
function readLegacyTaskRecords(projectRoot, configPath = resolve(projectRoot, ".rig", "task-config.json")) {
|
|
44
|
+
if (!existsSync(configPath)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const rawConfig = readLegacyTaskConfigJson(projectRoot, configPath);
|
|
48
|
+
return Object.entries(stripLegacyTaskConfigMetadata(rawConfig)).map(([id, entry]) => legacyTaskConfigEntryToRecord(id, entry)).filter((record) => record !== null);
|
|
49
|
+
}
|
|
50
|
+
function readLegacyTaskConfigJson(projectRoot, configPath) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
53
|
+
if (isPlainRecord(parsed)) {
|
|
54
|
+
return parsed;
|
|
55
|
+
}
|
|
56
|
+
throw new Error("task config root must be a JSON object");
|
|
57
|
+
} catch (cause) {
|
|
58
|
+
throw new LegacyTaskConfigReadError({
|
|
59
|
+
projectRoot,
|
|
60
|
+
configPath,
|
|
61
|
+
message: `Could not read legacy task config at ${configPath} for project ${projectRoot}: ${cause instanceof Error ? cause.message : String(cause)}`,
|
|
62
|
+
cause
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function stripLegacyTaskConfigMetadata(raw) {
|
|
67
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
68
|
+
return tasks;
|
|
69
|
+
}
|
|
70
|
+
function legacyTaskConfigEntryToRecord(id, entry) {
|
|
71
|
+
if (!isPlainRecord(entry)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const deps = firstStringList(entry.deps, entry.dependencies, entry.validation_deps, entry.validationDeps);
|
|
75
|
+
const validation = readStringList(entry.validation);
|
|
76
|
+
const validators = readStringList(entry.validators);
|
|
77
|
+
const scope = readStringList(entry.scope);
|
|
78
|
+
const status = typeof entry.status === "string" ? entry.status : "open";
|
|
79
|
+
const title = typeof entry.title === "string" ? entry.title : undefined;
|
|
80
|
+
const description = typeof entry.description === "string" ? entry.description : undefined;
|
|
81
|
+
const acceptanceCriteria = typeof entry.acceptance_criteria === "string" ? entry.acceptance_criteria : typeof entry.acceptanceCriteria === "string" ? entry.acceptanceCriteria : undefined;
|
|
82
|
+
return {
|
|
83
|
+
id,
|
|
84
|
+
deps,
|
|
85
|
+
status,
|
|
86
|
+
source: "legacy-task-config",
|
|
87
|
+
...title ? { title } : {},
|
|
88
|
+
...description ? { description } : {},
|
|
89
|
+
...acceptanceCriteria ? { acceptanceCriteria } : {},
|
|
90
|
+
...scope.length > 0 ? { scope } : {},
|
|
91
|
+
...validation.length > 0 ? { validation } : {},
|
|
92
|
+
...validators.length > 0 ? { validators } : {},
|
|
93
|
+
...preservedLegacyFields(entry)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function preservedLegacyFields(entry) {
|
|
97
|
+
const preserved = {};
|
|
98
|
+
for (const key of [
|
|
99
|
+
"role",
|
|
100
|
+
"browser",
|
|
101
|
+
"repo_pins",
|
|
102
|
+
"criticality",
|
|
103
|
+
"queue_weight",
|
|
104
|
+
"creates_repo",
|
|
105
|
+
"auto_synced"
|
|
106
|
+
]) {
|
|
107
|
+
if (entry[key] !== undefined) {
|
|
108
|
+
preserved[key] = entry[key];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return preserved;
|
|
112
|
+
}
|
|
113
|
+
function firstStringList(...candidates) {
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
const list = readStringList(candidate);
|
|
116
|
+
if (list.length > 0) {
|
|
117
|
+
return list;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
function readStringList(candidate) {
|
|
123
|
+
if (!Array.isArray(candidate)) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
return candidate.filter((value) => typeof value === "string");
|
|
127
|
+
}
|
|
128
|
+
function isPlainRecord(candidate) {
|
|
129
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// packages/runtime/src/control-plane/tasks/source-aware-task-config-source.ts
|
|
133
|
+
var STATUS_LABELS = new Set(["ready", "blocked", "in-progress", "under-review", "failed", "cancelled"]);
|
|
134
|
+
var FILE_TASK_PATTERN = /\.(task\.)?json$/;
|
|
135
|
+
function createSourceAwareTaskConfigRecordReader(projectRoot, options = {}) {
|
|
136
|
+
const configPath = options.configPath ?? resolve2(projectRoot, ".rig", "task-config.json");
|
|
137
|
+
const legacy = createLegacyTaskConfigRecordReader(projectRoot, { configPath });
|
|
138
|
+
const spawnFn = options.spawn ?? spawnSync;
|
|
139
|
+
const ghBinary = options.ghBinary ?? "gh";
|
|
140
|
+
const allowLocalFallback = options.allowLocalTaskConfigStatusFallback ?? true;
|
|
141
|
+
return {
|
|
142
|
+
async listTasks() {
|
|
143
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
144
|
+
if (!rawConfig) {
|
|
145
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
146
|
+
return configuredFilesPath ? listFileBackedTasks(projectRoot, configuredFilesPath) : [];
|
|
147
|
+
}
|
|
148
|
+
const tasks = [];
|
|
149
|
+
const legacyTasks = await legacy.listTasks();
|
|
150
|
+
const legacyById = new Map(legacyTasks.map((task) => [task.id, task]));
|
|
151
|
+
for (const [id, rawEntry] of Object.entries(stripLegacyTaskConfigMetadata2(rawConfig))) {
|
|
152
|
+
if (!isPlainRecord2(rawEntry)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
156
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
157
|
+
tasks.push(readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry));
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
161
|
+
const fileTask = readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
162
|
+
if (fileTask)
|
|
163
|
+
tasks.push(fileTask);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!allowLocalFallback) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const legacyTask = legacyById.get(id);
|
|
170
|
+
if (legacyTask) {
|
|
171
|
+
tasks.push(legacyTask);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return tasks;
|
|
175
|
+
},
|
|
176
|
+
async getTask(id) {
|
|
177
|
+
const rawEntry = readRawTaskEntry(configPath, id);
|
|
178
|
+
if (!rawEntry) {
|
|
179
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
180
|
+
return configuredFilesPath ? readFileBackedTask(projectRoot, configuredFilesPath, id, {}) : null;
|
|
181
|
+
}
|
|
182
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
183
|
+
if (metadata.taskSource?.kind === "github-issues") {
|
|
184
|
+
return readGithubIssueTask(ghBinary, spawnFn, id, metadata, rawEntry);
|
|
185
|
+
}
|
|
186
|
+
if (metadata.taskSource?.kind === "files" && metadata.taskSource.path) {
|
|
187
|
+
return readFileBackedTask(projectRoot, metadata.taskSource.path, id, rawEntry);
|
|
188
|
+
}
|
|
189
|
+
return allowLocalFallback ? legacy.getTask(id) : null;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
async function readSourceAwareTaskStatus(projectRoot, taskId, options = {}) {
|
|
194
|
+
try {
|
|
195
|
+
const task = await createSourceAwareTaskConfigRecordReader(projectRoot, options).getTask(taskId);
|
|
196
|
+
return task?.status ?? null;
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function updateSourceAwareTaskConfigTask(projectRoot, taskId, update, options = {}) {
|
|
202
|
+
const configPath = options.configPath ?? resolve2(projectRoot, ".rig", "task-config.json");
|
|
203
|
+
const rawEntry = readRawTaskEntry(configPath, taskId);
|
|
204
|
+
if (!rawEntry) {
|
|
205
|
+
const configuredFilesPath = readConfiguredFilesTaskSourcePath(projectRoot);
|
|
206
|
+
return configuredFilesPath ? updateFileBackedTask(projectRoot, configuredFilesPath, taskId, update) : false;
|
|
207
|
+
}
|
|
208
|
+
const metadata = readMaterializedTaskMetadata(rawEntry);
|
|
209
|
+
const source = metadata.taskSource;
|
|
210
|
+
if (source?.kind === "github-issues") {
|
|
211
|
+
applyGithubIssueUpdate(options.ghBinary ?? "gh", options.spawn ?? spawnSync, taskId, metadata, update);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (source?.kind === "files" && source.path) {
|
|
215
|
+
return updateFileBackedTask(projectRoot, source.path, taskId, update);
|
|
216
|
+
}
|
|
217
|
+
if (source?.kind && source.kind !== "files" && source.kind !== "legacy-task-config") {
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
if (!source && options.allowLocalTaskConfigStatusFallback === false) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (typeof update.status !== "string" || update.status.trim().length === 0) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
writeLegacyTaskStatus(configPath, taskId, update.status);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
function readMaterializedTaskMetadata(entry) {
|
|
230
|
+
const rawRig = entry._rig;
|
|
231
|
+
if (!isPlainRecord2(rawRig)) {
|
|
232
|
+
return {};
|
|
233
|
+
}
|
|
234
|
+
const rawSource = rawRig.taskSource;
|
|
235
|
+
const metadata = {};
|
|
236
|
+
if (isPlainRecord2(rawSource)) {
|
|
237
|
+
const kind = typeof rawSource.kind === "string" ? rawSource.kind : "";
|
|
238
|
+
if (kind.length > 0) {
|
|
239
|
+
metadata.taskSource = {
|
|
240
|
+
kind,
|
|
241
|
+
...typeof rawSource.path === "string" ? { path: rawSource.path } : {},
|
|
242
|
+
...typeof rawSource.owner === "string" ? { owner: rawSource.owner } : {},
|
|
243
|
+
...typeof rawSource.repo === "string" ? { repo: rawSource.repo } : {},
|
|
244
|
+
...Array.isArray(rawSource.labels) ? { labels: rawSource.labels.filter((label) => typeof label === "string") } : {},
|
|
245
|
+
...rawSource.state === "open" || rawSource.state === "closed" || rawSource.state === "all" ? { state: rawSource.state } : {}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (typeof rawRig.sourceIssueId === "string") {
|
|
250
|
+
metadata.sourceIssueId = rawRig.sourceIssueId;
|
|
251
|
+
}
|
|
252
|
+
return metadata;
|
|
253
|
+
}
|
|
254
|
+
function readConfiguredFilesTaskSourcePath(projectRoot) {
|
|
255
|
+
const jsonPath = resolve2(projectRoot, "rig.config.json");
|
|
256
|
+
if (existsSync2(jsonPath)) {
|
|
257
|
+
try {
|
|
258
|
+
const parsed = JSON.parse(readFileSync2(jsonPath, "utf8"));
|
|
259
|
+
if (isPlainRecord2(parsed) && isPlainRecord2(parsed.taskSource)) {
|
|
260
|
+
const source = parsed.taskSource;
|
|
261
|
+
return source.kind === "files" && typeof source.path === "string" ? source.path : null;
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
const tsPath = resolve2(projectRoot, "rig.config.ts");
|
|
268
|
+
if (!existsSync2(tsPath)) {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const source = readFileSync2(tsPath, "utf8");
|
|
273
|
+
const taskSourceBlock = source.match(/taskSource\s*:\s*\{[\s\S]*?\}/m)?.[0] ?? "";
|
|
274
|
+
const kind = taskSourceBlock.match(/kind\s*:\s*["']([^"']+)["']/)?.[1];
|
|
275
|
+
if (kind !== "files") {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return taskSourceBlock.match(/path\s*:\s*["']([^"']+)["']/)?.[1] ?? null;
|
|
279
|
+
} catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function readRawTaskEntry(configPath, taskId) {
|
|
284
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
285
|
+
if (!rawConfig) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
const entry = stripLegacyTaskConfigMetadata2(rawConfig)[taskId];
|
|
289
|
+
return isPlainRecord2(entry) ? entry : null;
|
|
290
|
+
}
|
|
291
|
+
function readRawTaskConfig(configPath) {
|
|
292
|
+
if (!existsSync2(configPath)) {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
|
|
296
|
+
return isPlainRecord2(parsed) ? parsed : null;
|
|
297
|
+
}
|
|
298
|
+
function stripLegacyTaskConfigMetadata2(raw) {
|
|
299
|
+
const { validation_descriptions: _legacyDescriptions, _meta, ...tasks } = raw;
|
|
300
|
+
return tasks;
|
|
301
|
+
}
|
|
302
|
+
function writeLegacyTaskStatus(configPath, taskId, status) {
|
|
303
|
+
const rawConfig = readRawTaskConfig(configPath);
|
|
304
|
+
if (!rawConfig) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const entry = rawConfig[taskId];
|
|
308
|
+
if (!isPlainRecord2(entry)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
entry.status = status;
|
|
312
|
+
writeFileSync(configPath, `${JSON.stringify(rawConfig, null, 2)}
|
|
313
|
+
`, "utf8");
|
|
314
|
+
}
|
|
315
|
+
function updateFileBackedTask(projectRoot, sourcePath, taskId, update) {
|
|
316
|
+
const directory = resolve2(projectRoot, sourcePath);
|
|
317
|
+
const file = findFileBackedTaskFile(directory, taskId);
|
|
318
|
+
if (!file) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
const raw = JSON.parse(readFileSync2(file, "utf8"));
|
|
322
|
+
if (!isPlainRecord2(raw)) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
if (update.status)
|
|
326
|
+
raw.status = update.status;
|
|
327
|
+
if (update.title !== undefined)
|
|
328
|
+
raw.title = update.title;
|
|
329
|
+
if (update.body !== undefined)
|
|
330
|
+
raw.body = update.body;
|
|
331
|
+
if (update.comment?.trim()) {
|
|
332
|
+
const existing = Array.isArray(raw.comments) ? raw.comments : [];
|
|
333
|
+
raw.comments = [
|
|
334
|
+
...existing,
|
|
335
|
+
{ body: update.comment, createdAt: new Date().toISOString(), source: "rig" }
|
|
336
|
+
];
|
|
337
|
+
}
|
|
338
|
+
writeFileSync(file, `${JSON.stringify(raw, null, 2)}
|
|
339
|
+
`, "utf8");
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
function listFileBackedTasks(projectRoot, sourcePath) {
|
|
343
|
+
const directory = resolve2(projectRoot, sourcePath);
|
|
344
|
+
if (!existsSync2(directory)) {
|
|
345
|
+
return [];
|
|
346
|
+
}
|
|
347
|
+
const tasks = [];
|
|
348
|
+
for (const name of readdirSync(directory)) {
|
|
349
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
350
|
+
continue;
|
|
351
|
+
const inferredId = basename(name).replace(FILE_TASK_PATTERN, "");
|
|
352
|
+
const task = readFileBackedTask(projectRoot, sourcePath, inferredId, {});
|
|
353
|
+
if (task)
|
|
354
|
+
tasks.push(task);
|
|
355
|
+
}
|
|
356
|
+
return tasks;
|
|
357
|
+
}
|
|
358
|
+
function readFileBackedTask(projectRoot, sourcePath, taskId, rawEntry) {
|
|
359
|
+
const file = findFileBackedTaskFile(resolve2(projectRoot, sourcePath), taskId);
|
|
360
|
+
if (!file) {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
const raw = JSON.parse(readFileSync2(file, "utf8"));
|
|
364
|
+
if (!isPlainRecord2(raw)) {
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
return {
|
|
368
|
+
id: typeof raw.id === "string" ? raw.id : taskId,
|
|
369
|
+
deps: Array.isArray(raw.deps) ? raw.deps : Array.isArray(raw.depends_on) ? raw.depends_on : [],
|
|
370
|
+
status: typeof raw.status === "string" ? raw.status : "ready",
|
|
371
|
+
title: typeof raw.title === "string" ? raw.title : typeof rawEntry.title === "string" ? rawEntry.title : taskId,
|
|
372
|
+
...raw
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function findFileBackedTaskFile(directory, taskId) {
|
|
376
|
+
if (!existsSync2(directory)) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
for (const name of readdirSync(directory)) {
|
|
380
|
+
if (!FILE_TASK_PATTERN.test(name))
|
|
381
|
+
continue;
|
|
382
|
+
const file = join(directory, name);
|
|
383
|
+
try {
|
|
384
|
+
if (!statSync(file).isFile())
|
|
385
|
+
continue;
|
|
386
|
+
const raw = JSON.parse(readFileSync2(file, "utf8"));
|
|
387
|
+
const inferredId = basename(file).replace(FILE_TASK_PATTERN, "");
|
|
388
|
+
const id = isPlainRecord2(raw) && typeof raw.id === "string" ? raw.id : inferredId;
|
|
389
|
+
if (id === taskId) {
|
|
390
|
+
return file;
|
|
391
|
+
}
|
|
392
|
+
} catch {}
|
|
393
|
+
}
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
function readGithubIssueTask(bin, spawnFn, id, metadata, rawEntry) {
|
|
397
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
398
|
+
const issue = runGh(bin, [
|
|
399
|
+
"issue",
|
|
400
|
+
"view",
|
|
401
|
+
String(id),
|
|
402
|
+
"--repo",
|
|
403
|
+
`${source.owner}/${source.repo}`,
|
|
404
|
+
"--json",
|
|
405
|
+
"number,title,body,labels,state,url,assignees"
|
|
406
|
+
], spawnFn);
|
|
407
|
+
return githubIssueToTask(issue, source, rawEntry);
|
|
408
|
+
}
|
|
409
|
+
function applyGithubIssueUpdate(bin, spawnFn, id, metadata, update) {
|
|
410
|
+
const source = requireGithubIssueSource(metadata, id);
|
|
411
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
412
|
+
if (update.status) {
|
|
413
|
+
applyGithubIssueStatus(bin, repo, spawnFn, id, update.status);
|
|
414
|
+
}
|
|
415
|
+
if (typeof update.comment === "string" && update.comment.trim().length > 0) {
|
|
416
|
+
runGhVoid(bin, ["issue", "comment", String(id), "--repo", repo, "--body", update.comment], spawnFn);
|
|
417
|
+
}
|
|
418
|
+
const editArgs = ["issue", "edit", String(id), "--repo", repo];
|
|
419
|
+
if (typeof update.title === "string" && update.title.trim().length > 0) {
|
|
420
|
+
editArgs.push("--title", update.title.trim());
|
|
421
|
+
}
|
|
422
|
+
if (typeof update.body === "string") {
|
|
423
|
+
editArgs.push("--body", update.body);
|
|
424
|
+
}
|
|
425
|
+
if (editArgs.length > 5) {
|
|
426
|
+
runGhVoid(bin, editArgs, spawnFn);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function requireGithubIssueSource(metadata, id) {
|
|
430
|
+
const source = metadata.taskSource;
|
|
431
|
+
if (source?.kind === "github-issues" && source.owner && source.repo) {
|
|
432
|
+
return { owner: source.owner, repo: source.repo };
|
|
433
|
+
}
|
|
434
|
+
const parsed = metadata.sourceIssueId?.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
435
|
+
if (parsed && parsed[3] === id) {
|
|
436
|
+
return { owner: parsed[1], repo: parsed[2] };
|
|
437
|
+
}
|
|
438
|
+
throw new Error(`Task ${id} is marked as github-issues but has no owner/repo source metadata`);
|
|
439
|
+
}
|
|
440
|
+
function githubIssueToTask(issue, source, rawEntry) {
|
|
441
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
442
|
+
const scope = labelNames.filter((label) => label.startsWith("scope:")).map((label) => label.slice("scope:".length));
|
|
443
|
+
const roleLabel = labelNames.find((label) => label.startsWith("role:"));
|
|
444
|
+
const validators = labelNames.filter((label) => label.startsWith("validator:")).map((label) => label.slice("validator:".length));
|
|
445
|
+
const body = issue.body ?? "";
|
|
446
|
+
const repo = `${source.owner}/${source.repo}`;
|
|
447
|
+
return {
|
|
448
|
+
id: String(issue.number),
|
|
449
|
+
deps: parseDeps(body),
|
|
450
|
+
status: githubStatusFor(issue),
|
|
451
|
+
title: issue.title,
|
|
452
|
+
body,
|
|
453
|
+
...scope.length > 0 ? { scope } : {},
|
|
454
|
+
...roleLabel ? { role: roleLabel.slice("role:".length) } : typeof rawEntry.role === "string" ? { role: rawEntry.role } : {},
|
|
455
|
+
...validators.length > 0 ? { validators } : {},
|
|
456
|
+
...issue.url ? { url: issue.url } : {},
|
|
457
|
+
issueType: issueTypeFor(labelNames),
|
|
458
|
+
sourceIssueId: `${repo}#${issue.number}`,
|
|
459
|
+
parentChildDeps: parseParents(body),
|
|
460
|
+
labels: labelNames,
|
|
461
|
+
raw: issue,
|
|
462
|
+
source: "github-issues",
|
|
463
|
+
_rig: {
|
|
464
|
+
taskSource: { kind: "github-issues", owner: source.owner, repo: source.repo },
|
|
465
|
+
sourceIssueId: `${repo}#${issue.number}`
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function githubStatusFor(issue) {
|
|
470
|
+
const state = (issue.state ?? "").toUpperCase();
|
|
471
|
+
if (state === "CLOSED")
|
|
472
|
+
return "closed";
|
|
473
|
+
const labelNames = (issue.labels ?? []).map((label) => label.name);
|
|
474
|
+
if (labelNames.includes("in-progress"))
|
|
475
|
+
return "in_progress";
|
|
476
|
+
if (labelNames.includes("blocked"))
|
|
477
|
+
return "blocked";
|
|
478
|
+
if (labelNames.includes("ready"))
|
|
479
|
+
return "ready";
|
|
480
|
+
if (labelNames.includes("under-review"))
|
|
481
|
+
return "under_review";
|
|
482
|
+
if (labelNames.includes("failed"))
|
|
483
|
+
return "failed";
|
|
484
|
+
if (labelNames.includes("cancelled"))
|
|
485
|
+
return "cancelled";
|
|
486
|
+
return "open";
|
|
487
|
+
}
|
|
488
|
+
function applyGithubIssueStatus(bin, repo, spawnFn, id, status) {
|
|
489
|
+
if (status === "closed") {
|
|
490
|
+
runGhVoid(bin, ["issue", "close", String(id), "--repo", repo], spawnFn);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const targetLabel = statusLabelFor(status);
|
|
494
|
+
for (const label of STATUS_LABELS) {
|
|
495
|
+
if (targetLabel !== null && label === targetLabel) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
try {
|
|
499
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--remove-label", label], spawnFn);
|
|
500
|
+
} catch {}
|
|
501
|
+
}
|
|
502
|
+
if (targetLabel !== null) {
|
|
503
|
+
try {
|
|
504
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
|
|
505
|
+
} catch (error) {
|
|
506
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
507
|
+
if (!/not found/i.test(message)) {
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
ensureStatusLabel(bin, repo, spawnFn, targetLabel);
|
|
511
|
+
runGhVoid(bin, ["issue", "edit", String(id), "--repo", repo, "--add-label", targetLabel], spawnFn);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function statusLabelFor(status) {
|
|
516
|
+
switch (status) {
|
|
517
|
+
case "in_progress":
|
|
518
|
+
return "in-progress";
|
|
519
|
+
case "blocked":
|
|
520
|
+
return "blocked";
|
|
521
|
+
case "ready":
|
|
522
|
+
return "ready";
|
|
523
|
+
case "under_review":
|
|
524
|
+
return "under-review";
|
|
525
|
+
case "failed":
|
|
526
|
+
return "failed";
|
|
527
|
+
case "cancelled":
|
|
528
|
+
return "cancelled";
|
|
529
|
+
case "open":
|
|
530
|
+
return null;
|
|
531
|
+
default:
|
|
532
|
+
throw new Error(`unsupported status: ${status}`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function ensureStatusLabel(bin, repo, spawnFn, label) {
|
|
536
|
+
try {
|
|
537
|
+
runGhVoid(bin, [
|
|
538
|
+
"label",
|
|
539
|
+
"create",
|
|
540
|
+
label,
|
|
541
|
+
"--repo",
|
|
542
|
+
repo,
|
|
543
|
+
"--color",
|
|
544
|
+
"6f42c1",
|
|
545
|
+
"--description",
|
|
546
|
+
"Task status managed by Rig"
|
|
547
|
+
], spawnFn);
|
|
548
|
+
} catch (error) {
|
|
549
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
550
|
+
if (!/already exists/i.test(message)) {
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
function selectedGitHubEnv() {
|
|
556
|
+
const token = process.env.RIG_GITHUB_SELECTED_TOKEN?.trim() ?? "";
|
|
557
|
+
return { GH_TOKEN: token, GITHUB_TOKEN: token };
|
|
558
|
+
}
|
|
559
|
+
function ghSpawnOptions() {
|
|
560
|
+
return { encoding: "utf-8", env: { ...process.env, ...selectedGitHubEnv() } };
|
|
561
|
+
}
|
|
562
|
+
function runGh(bin, args, spawnFn) {
|
|
563
|
+
const res = spawnFn(bin, [...args], ghSpawnOptions());
|
|
564
|
+
assertGhSuccess(args, res);
|
|
565
|
+
if (!res.stdout || res.stdout.trim() === "") {
|
|
566
|
+
throw new Error(`gh ${args.join(" ")} returned empty stdout`);
|
|
567
|
+
}
|
|
568
|
+
return JSON.parse(res.stdout);
|
|
569
|
+
}
|
|
570
|
+
function runGhVoid(bin, args, spawnFn) {
|
|
571
|
+
const res = spawnFn(bin, [...args], ghSpawnOptions());
|
|
572
|
+
assertGhSuccess(args, res);
|
|
573
|
+
}
|
|
574
|
+
function assertGhSuccess(args, res) {
|
|
575
|
+
if (res.error) {
|
|
576
|
+
const msg = res.error.message ?? String(res.error);
|
|
577
|
+
throw new Error(`gh CLI not available \u2014 install gh (brew install gh / apt install gh): ${msg}`);
|
|
578
|
+
}
|
|
579
|
+
if (res.status !== 0) {
|
|
580
|
+
throw new Error(`gh ${args.join(" ")} failed (exit ${res.status}): ${res.stderr}`);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
function parseDeps(body) {
|
|
584
|
+
return parseIssueRefs(body, /^depends-on:\s*([^\n]+)/im);
|
|
585
|
+
}
|
|
586
|
+
function parseParents(body) {
|
|
587
|
+
return parseIssueRefs(body, /^parents?:\s*([^\n]+)/im);
|
|
588
|
+
}
|
|
589
|
+
function parseIssueRefs(body, pattern) {
|
|
590
|
+
const match = body.match(pattern);
|
|
591
|
+
if (!match)
|
|
592
|
+
return [];
|
|
593
|
+
return match[1].split(",").map((value) => value.trim()).map((value) => value.replace(/^#/, "").match(/^(\d+)/)?.[1] ?? "").filter((value) => value.length > 0);
|
|
594
|
+
}
|
|
595
|
+
function issueTypeFor(labels) {
|
|
596
|
+
const typed = labels.find((label) => label.startsWith("type:"));
|
|
597
|
+
if (typed)
|
|
598
|
+
return typed.slice("type:".length);
|
|
599
|
+
if (labels.includes("epic"))
|
|
600
|
+
return "epic";
|
|
601
|
+
return "task";
|
|
602
|
+
}
|
|
603
|
+
function isPlainRecord2(candidate) {
|
|
604
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
605
|
+
}
|
|
606
|
+
export {
|
|
607
|
+
updateSourceAwareTaskConfigTask,
|
|
608
|
+
readSourceAwareTaskStatus,
|
|
609
|
+
readMaterializedTaskMetadata,
|
|
610
|
+
createSourceAwareTaskConfigRecordReader
|
|
611
|
+
};
|