@fitlab-ai/agent-infra 0.9.5 → 0.9.7
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/dist/bin/internal-cli.js +8 -3
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +171 -20
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +68 -8
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -4
- package/dist/lib/internal/codex-lifecycle.js +169 -3
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/release-workflow.js +108 -11
- package/dist/lib/internal/sandbox-control.js +43 -6
- package/dist/lib/internal/task-create.js +12 -4
- package/dist/lib/internal/task-orchestration.js +40 -7
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/platform/issue-comments.js +2 -1
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/release/workflow.js +29 -21
- package/dist/lib/sandbox/commands/create.js +111 -60
- package/dist/lib/sandbox/commands/prune.js +40 -16
- package/dist/lib/sandbox/commands/rm.js +372 -94
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/client.js +138 -22
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +142 -0
- package/dist/lib/sandbox/control/lifecycle.js +557 -0
- package/dist/lib/sandbox/control/protocol.js +69 -26
- package/dist/lib/sandbox/control/server.js +395 -99
- package/dist/lib/sandbox/control/state.js +191 -0
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/managed-fs.js +14 -2
- package/dist/lib/sandbox/mounts.js +22 -19
- package/dist/lib/sandbox/recovery.js +284 -50
- package/dist/lib/sandbox/workspace-view.js +90 -10
- package/dist/lib/sandbox/worktree-safety.js +196 -0
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +17 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +87 -15
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +236 -0
- package/dist/lib/task/commands/status.js +3 -0
- package/dist/lib/task/commands/validate.js +289 -0
- package/dist/lib/task/commit-finalization.js +36 -9
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +109 -2
- package/dist/lib/task/events.js +149 -6
- package/dist/lib/task/finalization-artifacts.js +2 -1
- package/dist/lib/task/implementation-inputs.js +1 -7
- package/dist/lib/task/index.js +9 -0
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +365 -15
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +78 -14
- package/dist/lib/task/verification.js +1 -0
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +202 -18
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -3
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +83 -6
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -3
- package/lib/internal/codex-lifecycle.ts +175 -4
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/release-workflow.ts +107 -11
- package/lib/internal/sandbox-control.ts +38 -6
- package/lib/internal/task-create.ts +16 -4
- package/lib/internal/task-orchestration.ts +34 -7
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/platform/issue-comments.ts +2 -1
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/release/workflow.ts +69 -20
- package/lib/sandbox/commands/create.ts +70 -25
- package/lib/sandbox/commands/prune.ts +53 -15
- package/lib/sandbox/commands/rm.ts +423 -108
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/client.ts +152 -28
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +167 -0
- package/lib/sandbox/control/lifecycle.ts +573 -0
- package/lib/sandbox/control/protocol.ts +121 -68
- package/lib/sandbox/control/server.ts +398 -109
- package/lib/sandbox/control/state.ts +213 -0
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/managed-fs.ts +17 -2
- package/lib/sandbox/mounts.ts +16 -8
- package/lib/sandbox/recovery.ts +335 -65
- package/lib/sandbox/workspace-view.ts +102 -11
- package/lib/sandbox/worktree-safety.ts +234 -0
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +24 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +78 -14
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +319 -0
- package/lib/task/commands/status.ts +2 -0
- package/lib/task/commands/validate.ts +282 -0
- package/lib/task/commit-finalization.ts +52 -9
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +208 -7
- package/lib/task/events.ts +131 -5
- package/lib/task/finalization-artifacts.ts +2 -1
- package/lib/task/implementation-inputs.ts +1 -5
- package/lib/task/index.ts +8 -0
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +552 -18
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +93 -24
- package/lib/task/verification.ts +2 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +72 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -0
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/post-release/SKILL.en.md +32 -6
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +32 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +42 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +42 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +25 -0
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +69 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +69 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -3
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.claude/commands/run-manual-validation.en.md +9 -0
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +9 -0
- package/templates/.codex/hooks.json +8 -8
- package/templates/.opencode/commands/run-manual-validation.en.md +11 -0
- package/templates/.opencode/commands/run-manual-validation.zh-CN.md +11 -0
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runProbe } from './shell.js';
|
|
5
|
+
function probeText(probe, cwd, args) {
|
|
6
|
+
const result = probe('git', ['-C', cwd, ...args], { encoding: 'utf8' });
|
|
7
|
+
if (result.error)
|
|
8
|
+
throw result.error;
|
|
9
|
+
if (result.status !== 0) {
|
|
10
|
+
const stderr = typeof result.stderr === 'string' ? result.stderr.trim() : result.stderr.toString('utf8').trim();
|
|
11
|
+
throw new Error(stderr || `git ${args[0] ?? 'command'} failed with exit code ${result.status ?? 'unknown'}`);
|
|
12
|
+
}
|
|
13
|
+
return typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf8');
|
|
14
|
+
}
|
|
15
|
+
function parseStatus(raw) {
|
|
16
|
+
const records = raw.split('\0');
|
|
17
|
+
const changes = [];
|
|
18
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
19
|
+
const record = records[index] ?? '';
|
|
20
|
+
if (!record || record.startsWith('# '))
|
|
21
|
+
continue;
|
|
22
|
+
if (record.startsWith('? ')) {
|
|
23
|
+
changes.push({ indexStatus: '?', worktreeStatus: '?', path: record.slice(2) });
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (record.startsWith('1 ')) {
|
|
27
|
+
const match = /^1 (..) \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
|
|
28
|
+
if (!match)
|
|
29
|
+
throw new Error('Unable to parse git porcelain v2 ordinary record');
|
|
30
|
+
changes.push({ indexStatus: match[1][0], worktreeStatus: match[1][1], path: match[2] });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (record.startsWith('2 ')) {
|
|
34
|
+
const match = /^2 (..) \S+ \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
|
|
35
|
+
const originalPath = records[++index];
|
|
36
|
+
if (!match || originalPath === undefined || originalPath === '') {
|
|
37
|
+
throw new Error('Unable to parse git porcelain v2 rename record');
|
|
38
|
+
}
|
|
39
|
+
changes.push({
|
|
40
|
+
indexStatus: match[1][0],
|
|
41
|
+
worktreeStatus: match[1][1],
|
|
42
|
+
path: match[2],
|
|
43
|
+
originalPath
|
|
44
|
+
});
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (record.startsWith('u ')) {
|
|
48
|
+
const match = /^u (..) \S+ \S+ \S+ \S+ \S+ \S+ \S+ \S+ (.*)$/s.exec(record);
|
|
49
|
+
if (!match)
|
|
50
|
+
throw new Error('Unable to parse git porcelain v2 unmerged record');
|
|
51
|
+
changes.push({ indexStatus: match[1][0], worktreeStatus: match[1][1], path: match[2] });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Unsupported git porcelain v2 record '${record[0] ?? ''}'`);
|
|
55
|
+
}
|
|
56
|
+
return changes;
|
|
57
|
+
}
|
|
58
|
+
function updatePart(hash, label, value) {
|
|
59
|
+
const data = Buffer.isBuffer(value) ? value : Buffer.from(value, 'utf8');
|
|
60
|
+
hash.update(`${label.length}:${label}:${data.length}:`, 'utf8');
|
|
61
|
+
hash.update(data);
|
|
62
|
+
}
|
|
63
|
+
function resolveSnapshotPath(worktree, relativePath) {
|
|
64
|
+
const resolved = path.resolve(worktree, relativePath);
|
|
65
|
+
const relative = path.relative(worktree, resolved);
|
|
66
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
67
|
+
throw new Error(`Git reported a path outside the worktree: ${JSON.stringify(relativePath)}`);
|
|
68
|
+
}
|
|
69
|
+
return resolved;
|
|
70
|
+
}
|
|
71
|
+
function hashPath(hash, worktree, relativePath, probe) {
|
|
72
|
+
updatePart(hash, 'path', relativePath);
|
|
73
|
+
const target = resolveSnapshotPath(worktree, relativePath);
|
|
74
|
+
let stat;
|
|
75
|
+
try {
|
|
76
|
+
stat = fs.lstatSync(target);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code === 'ENOENT') {
|
|
80
|
+
updatePart(hash, 'kind', 'absent');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
updatePart(hash, 'mode', String(stat.mode & 0o7777));
|
|
86
|
+
if (stat.isFile()) {
|
|
87
|
+
updatePart(hash, 'kind', 'file');
|
|
88
|
+
updatePart(hash, 'content', fs.readFileSync(target));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (stat.isSymbolicLink()) {
|
|
92
|
+
updatePart(hash, 'kind', 'symlink');
|
|
93
|
+
updatePart(hash, 'target', fs.readlinkSync(target));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (stat.isDirectory()) {
|
|
97
|
+
updatePart(hash, 'kind', 'git-directory');
|
|
98
|
+
const nestedHead = probeText(probe, target, ['rev-parse', '--verify', 'HEAD']).trim();
|
|
99
|
+
const nestedStatus = probeText(probe, target, [
|
|
100
|
+
'status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--ignore-submodules=none'
|
|
101
|
+
]);
|
|
102
|
+
const nestedIndex = probeText(probe, target, ['ls-files', '--stage', '-z']);
|
|
103
|
+
updatePart(hash, 'nested-head', nestedHead);
|
|
104
|
+
updatePart(hash, 'nested-status', nestedStatus);
|
|
105
|
+
updatePart(hash, 'nested-index', nestedIndex);
|
|
106
|
+
for (const change of parseStatus(nestedStatus)) {
|
|
107
|
+
hashPath(hash, target, change.path, probe);
|
|
108
|
+
if (change.originalPath)
|
|
109
|
+
hashPath(hash, target, change.originalPath, probe);
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
throw new Error(`Unsupported file type in dirty snapshot: ${JSON.stringify(relativePath)}`);
|
|
114
|
+
}
|
|
115
|
+
function captureOnce(worktree, probe) {
|
|
116
|
+
const resolvedWorktree = path.resolve(worktree);
|
|
117
|
+
const branch = probeText(probe, resolvedWorktree, ['rev-parse', '--abbrev-ref', 'HEAD']).trim();
|
|
118
|
+
const head = probeText(probe, resolvedWorktree, ['rev-parse', '--verify', 'HEAD']).trim();
|
|
119
|
+
const status = probeText(probe, resolvedWorktree, [
|
|
120
|
+
'status', '--porcelain=v2', '-z', '--branch', '--untracked-files=all', '--ignore-submodules=none'
|
|
121
|
+
]);
|
|
122
|
+
const index = probeText(probe, resolvedWorktree, ['ls-files', '--stage', '-z']);
|
|
123
|
+
if (!branch)
|
|
124
|
+
throw new Error('Unable to determine worktree branch');
|
|
125
|
+
if (!head)
|
|
126
|
+
throw new Error('Unable to determine worktree HEAD');
|
|
127
|
+
const changes = parseStatus(status);
|
|
128
|
+
const hash = createHash('sha256');
|
|
129
|
+
updatePart(hash, 'worktree', resolvedWorktree);
|
|
130
|
+
updatePart(hash, 'branch', branch);
|
|
131
|
+
updatePart(hash, 'head', head);
|
|
132
|
+
updatePart(hash, 'status', status);
|
|
133
|
+
updatePart(hash, 'index', index);
|
|
134
|
+
for (const change of changes) {
|
|
135
|
+
hashPath(hash, resolvedWorktree, change.path, probe);
|
|
136
|
+
if (change.originalPath)
|
|
137
|
+
hashPath(hash, resolvedWorktree, change.originalPath, probe);
|
|
138
|
+
}
|
|
139
|
+
return { worktree: resolvedWorktree, branch, head, changes, identity: hash.digest('hex') };
|
|
140
|
+
}
|
|
141
|
+
function inspectWorktree(worktree, { probe = runProbe } = {}) {
|
|
142
|
+
const resolvedWorktree = path.resolve(worktree);
|
|
143
|
+
try {
|
|
144
|
+
const first = captureOnce(resolvedWorktree, probe);
|
|
145
|
+
const second = captureOnce(resolvedWorktree, probe);
|
|
146
|
+
if (first.identity !== second.identity) {
|
|
147
|
+
return { status: 'failed', worktree: resolvedWorktree, message: 'Snapshot changed while inspecting worktree' };
|
|
148
|
+
}
|
|
149
|
+
return { status: second.changes.length === 0 ? 'clean' : 'dirty', snapshot: second };
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
status: 'failed',
|
|
154
|
+
worktree: resolvedWorktree,
|
|
155
|
+
message: error instanceof Error ? error.message : String(error)
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function inspectWorktrees(worktrees) {
|
|
160
|
+
return [...new Set(worktrees.map((worktree) => path.resolve(worktree)))].sort()
|
|
161
|
+
.map((worktree) => inspectWorktree(worktree));
|
|
162
|
+
}
|
|
163
|
+
function createCleanPermit(snapshot) {
|
|
164
|
+
if (snapshot.changes.length > 0)
|
|
165
|
+
throw new Error('Cannot create a clean permit for a dirty worktree');
|
|
166
|
+
return { mode: 'clean', snapshot };
|
|
167
|
+
}
|
|
168
|
+
function createDiscardPermit(snapshot) {
|
|
169
|
+
if (snapshot.changes.length === 0)
|
|
170
|
+
throw new Error('Cannot create a discard permit for a clean worktree');
|
|
171
|
+
return { mode: 'discard', snapshot };
|
|
172
|
+
}
|
|
173
|
+
function verifyWorktreePermit(permit, options = {}) {
|
|
174
|
+
const current = inspectWorktree(permit.snapshot.worktree, options);
|
|
175
|
+
if (current.status === 'failed')
|
|
176
|
+
throw new Error(`Unable to verify worktree permit: ${current.message}`);
|
|
177
|
+
if (current.snapshot.identity !== permit.snapshot.identity || (permit.mode === 'clean' && current.status !== 'clean')) {
|
|
178
|
+
throw new Error(`Worktree changed after authorization: ${permit.snapshot.worktree}`);
|
|
179
|
+
}
|
|
180
|
+
return current.snapshot;
|
|
181
|
+
}
|
|
182
|
+
function formatWorktreeSnapshot(snapshot) {
|
|
183
|
+
const lines = snapshot.changes.map((change) => {
|
|
184
|
+
const rename = change.originalPath ? ` <- ${JSON.stringify(change.originalPath)}` : '';
|
|
185
|
+
return ` ${change.indexStatus}${change.worktreeStatus} ${JSON.stringify(change.path)}${rename}`;
|
|
186
|
+
});
|
|
187
|
+
return [
|
|
188
|
+
`Worktree: ${JSON.stringify(snapshot.worktree)}`,
|
|
189
|
+
`Branch: ${snapshot.branch}`,
|
|
190
|
+
`HEAD: ${snapshot.head}`,
|
|
191
|
+
...lines,
|
|
192
|
+
`Snapshot identity: ${snapshot.identity}`
|
|
193
|
+
].join('\n');
|
|
194
|
+
}
|
|
195
|
+
export { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectWorktree, inspectWorktrees, verifyWorktreePermit };
|
|
196
|
+
//# sourceMappingURL=worktree-safety.js.map
|
|
@@ -4,6 +4,12 @@ import { loadServerConfig } from './config.js';
|
|
|
4
4
|
import { runDaemon } from './daemon.js';
|
|
5
5
|
import { getProcessStartTime, isProcessAlive, processIdentityMatches, readProcessState, removePidFileIfMatches, writePidRecord } from './process-state.js';
|
|
6
6
|
export { isProcessAlive } from './process-state.js';
|
|
7
|
+
export function buildProcessTreeStopCommand(pid, platform) {
|
|
8
|
+
if (platform === 'win32') {
|
|
9
|
+
return { kind: 'exec', command: 'taskkill', args: ['/PID', String(pid), '/T', '/F'] };
|
|
10
|
+
}
|
|
11
|
+
return { kind: 'group-signal', pid: -pid, signal: 'SIGTERM' };
|
|
12
|
+
}
|
|
7
13
|
export function buildStopCommand(pid, platform) {
|
|
8
14
|
if (platform === 'win32') {
|
|
9
15
|
return { kind: 'exec', command: 'taskkill', args: ['/PID', String(pid), '/T', '/F'] };
|
|
@@ -20,7 +26,7 @@ async function waitForProcessIdentity(pid, timeoutMs = 2000) {
|
|
|
20
26
|
while (Date.now() < deadline) {
|
|
21
27
|
const startTime = getProcessStartTime(pid);
|
|
22
28
|
if (startTime !== null)
|
|
23
|
-
return { version:
|
|
29
|
+
return { version: 2, pid, startTime };
|
|
24
30
|
await delay(25);
|
|
25
31
|
}
|
|
26
32
|
return null;
|
|
@@ -52,6 +58,9 @@ export async function start({ foreground = false } = {}) {
|
|
|
52
58
|
process.stdout.write(`server already running (pid ${existing.record.pid})\n`);
|
|
53
59
|
return;
|
|
54
60
|
}
|
|
61
|
+
if (existing.kind === 'legacy-json' || existing.kind === 'legacy-pid-only' || existing.kind === 'legacy-unknown') {
|
|
62
|
+
throw new Error('server: legacy pid record requires the matching previous version to stop it before starting');
|
|
63
|
+
}
|
|
55
64
|
if (existing.kind !== 'missing') {
|
|
56
65
|
removePidFileIfMatches(pidPath, existing.snapshot);
|
|
57
66
|
}
|
|
@@ -98,6 +107,9 @@ export async function stop() {
|
|
|
98
107
|
return;
|
|
99
108
|
}
|
|
100
109
|
if (state.kind !== 'running') {
|
|
110
|
+
if (state.kind === 'legacy-json' || state.kind === 'legacy-pid-only' || state.kind === 'legacy-unknown') {
|
|
111
|
+
throw new Error('server: legacy pid record requires the matching previous version to stop it');
|
|
112
|
+
}
|
|
101
113
|
removePidFileIfMatches(config.pidFile, state.snapshot);
|
|
102
114
|
process.stdout.write('server is not running (removed stale pid file)\n');
|
|
103
115
|
return;
|
|
@@ -129,6 +141,10 @@ export function status() {
|
|
|
129
141
|
const config = loadServerConfig();
|
|
130
142
|
const state = readProcessState(config.pidFile);
|
|
131
143
|
if (state.kind !== 'running') {
|
|
144
|
+
if (state.kind === 'legacy-json' || state.kind === 'legacy-pid-only' || state.kind === 'legacy-unknown') {
|
|
145
|
+
process.stdout.write('server: legacy pid record requires the matching previous version to stop it\n');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
132
148
|
if (state.kind !== 'missing')
|
|
133
149
|
removePidFileIfMatches(config.pidFile, state.snapshot);
|
|
134
150
|
process.stdout.write('server: stopped\n');
|
|
@@ -11,7 +11,10 @@ export function parseLinuxProcessStat(raw) {
|
|
|
11
11
|
if (typeof state !== 'string' || state.length !== 1 || !/^\d+$/.test(startTime ?? '')) {
|
|
12
12
|
return null;
|
|
13
13
|
}
|
|
14
|
-
|
|
14
|
+
const numericStartTime = Number(startTime);
|
|
15
|
+
if (!Number.isSafeInteger(numericStartTime))
|
|
16
|
+
return null;
|
|
17
|
+
return { state, startTime: numericStartTime };
|
|
15
18
|
}
|
|
16
19
|
export function buildProcessStartTimeQuery(pid, platform) {
|
|
17
20
|
if (platform === 'darwin') {
|
|
@@ -47,6 +50,17 @@ export function isProcessAlive(pid, platform = process.platform) {
|
|
|
47
50
|
return error.code === 'EPERM';
|
|
48
51
|
}
|
|
49
52
|
}
|
|
53
|
+
function parseDarwinStartTime(output) {
|
|
54
|
+
const match = output.trim().match(/^(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat) ([A-Z][a-z]{2}) (\d{1,2}) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/);
|
|
55
|
+
if (!match)
|
|
56
|
+
return null;
|
|
57
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
58
|
+
const month = months.indexOf(match[1]);
|
|
59
|
+
if (month < 0)
|
|
60
|
+
return null;
|
|
61
|
+
const timestamp = Date.UTC(Number(match[6]), month, Number(match[2]), Number(match[3]), Number(match[4]), Number(match[5]));
|
|
62
|
+
return Number.isNaN(timestamp) ? null : timestamp;
|
|
63
|
+
}
|
|
50
64
|
export function getProcessStartTime(pid, platform = process.platform) {
|
|
51
65
|
if (!Number.isInteger(pid) || pid <= 0)
|
|
52
66
|
return null;
|
|
@@ -65,14 +79,19 @@ export function getProcessStartTime(pid, platform = process.platform) {
|
|
|
65
79
|
if (query === null)
|
|
66
80
|
return null;
|
|
67
81
|
try {
|
|
68
|
-
const output = execFileSync(query.command, query.args, {
|
|
82
|
+
const output = execFileSync(query.command, query.args, {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
|
|
85
|
+
}).trim();
|
|
69
86
|
if (output.length === 0)
|
|
70
87
|
return null;
|
|
71
88
|
if (platform === 'win32') {
|
|
72
89
|
const timestamp = new Date(output);
|
|
73
|
-
return Number.isNaN(timestamp.getTime()) ? null : timestamp.
|
|
90
|
+
return Number.isNaN(timestamp.getTime()) ? null : timestamp.getTime();
|
|
74
91
|
}
|
|
75
|
-
|
|
92
|
+
if (platform === 'darwin')
|
|
93
|
+
return parseDarwinStartTime(output);
|
|
94
|
+
return null;
|
|
76
95
|
}
|
|
77
96
|
catch {
|
|
78
97
|
return null;
|
|
@@ -82,26 +101,42 @@ function parsePidRecord(raw) {
|
|
|
82
101
|
const trimmed = raw.trim();
|
|
83
102
|
if (/^[1-9]\d*$/.test(trimmed)) {
|
|
84
103
|
const pid = Number(trimmed);
|
|
85
|
-
return Number.isSafeInteger(pid)
|
|
104
|
+
return Number.isSafeInteger(pid)
|
|
105
|
+
? { kind: 'legacy-pid-only', legacy: { pid, raw, format: 'pid-only' } }
|
|
106
|
+
: { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'pid is not a safe integer' };
|
|
86
107
|
}
|
|
87
108
|
try {
|
|
88
109
|
const value = JSON.parse(trimmed);
|
|
110
|
+
const pidValue = value?.pid;
|
|
111
|
+
const startTimeValue = value?.startTime;
|
|
89
112
|
if (value !== null &&
|
|
90
|
-
value.version ===
|
|
91
|
-
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
value.version === 2 &&
|
|
114
|
+
typeof pidValue === 'number' &&
|
|
115
|
+
Number.isSafeInteger(pidValue) &&
|
|
116
|
+
pidValue > 0 &&
|
|
117
|
+
typeof startTimeValue === 'number' &&
|
|
118
|
+
Number.isSafeInteger(startTimeValue) &&
|
|
119
|
+
startTimeValue >= 0) {
|
|
120
|
+
const startTime = startTimeValue;
|
|
95
121
|
return {
|
|
96
122
|
kind: 'record',
|
|
97
|
-
record: { version:
|
|
123
|
+
record: { version: 2, pid: pidValue, startTime }
|
|
98
124
|
};
|
|
99
125
|
}
|
|
126
|
+
if (value !== null &&
|
|
127
|
+
value.version === 1 &&
|
|
128
|
+
typeof pidValue === 'number' &&
|
|
129
|
+
Number.isSafeInteger(pidValue) &&
|
|
130
|
+
pidValue > 0 &&
|
|
131
|
+
typeof value.startTime === 'string' &&
|
|
132
|
+
value.startTime.length > 0) {
|
|
133
|
+
return { kind: 'legacy-json', legacy: { pid: pidValue, raw, format: 'json-v1' } };
|
|
134
|
+
}
|
|
135
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'unsupported PID record schema' };
|
|
100
136
|
}
|
|
101
137
|
catch {
|
|
102
|
-
|
|
138
|
+
return { kind: 'legacy-unknown', legacy: { raw, format: 'malformed' }, reason: 'invalid PID record JSON' };
|
|
103
139
|
}
|
|
104
|
-
return { kind: 'invalid' };
|
|
105
140
|
}
|
|
106
141
|
export function readProcessState(pidFile) {
|
|
107
142
|
let snapshot;
|
|
@@ -111,13 +146,15 @@ export function readProcessState(pidFile) {
|
|
|
111
146
|
catch (error) {
|
|
112
147
|
if (error.code === 'ENOENT')
|
|
113
148
|
return { kind: 'missing', snapshot: null };
|
|
114
|
-
return { kind: '
|
|
149
|
+
return { kind: 'legacy-unknown', snapshot: '', legacy: { raw: '', format: 'malformed' }, reason: 'PID record cannot be read' };
|
|
115
150
|
}
|
|
116
151
|
const parsed = parsePidRecord(snapshot);
|
|
117
|
-
if (parsed.kind === '
|
|
118
|
-
return { kind:
|
|
119
|
-
if (parsed.kind === 'legacy')
|
|
120
|
-
return { kind:
|
|
152
|
+
if (parsed.kind === 'legacy-json')
|
|
153
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
154
|
+
if (parsed.kind === 'legacy-pid-only')
|
|
155
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy };
|
|
156
|
+
if (parsed.kind === 'legacy-unknown')
|
|
157
|
+
return { kind: parsed.kind, snapshot, legacy: parsed.legacy, reason: parsed.reason };
|
|
121
158
|
const currentStartTime = getProcessStartTime(parsed.record.pid);
|
|
122
159
|
if (currentStartTime === null || currentStartTime !== parsed.record.startTime) {
|
|
123
160
|
return { kind: 'stale', snapshot, record: parsed.record };
|
|
@@ -162,7 +199,7 @@ export function removePidRecordIfMatches(pidFile, expected) {
|
|
|
162
199
|
return false;
|
|
163
200
|
return removePidFileIfMatches(pidFile, state.snapshot);
|
|
164
201
|
}
|
|
165
|
-
export function processIdentityMatches(
|
|
166
|
-
return getProcessStartTime(
|
|
202
|
+
export function processIdentityMatches(identity) {
|
|
203
|
+
return getProcessStartTime(identity.pid) === identity.startTime;
|
|
167
204
|
}
|
|
168
205
|
//# sourceMappingURL=process-state.js.map
|
|
@@ -5,6 +5,7 @@ import { locateActivityLog } from './activity-log.js';
|
|
|
5
5
|
import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.js';
|
|
6
6
|
import { parseVerdict } from './review-artifacts.js';
|
|
7
7
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
8
|
+
import { receiptForOutput, sha256File } from './artifact-receipts.js';
|
|
8
9
|
const artifactFamilyCatalog = [
|
|
9
10
|
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
10
11
|
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
@@ -13,6 +14,7 @@ const artifactFamilyCatalog = [
|
|
|
13
14
|
{ family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
|
|
14
15
|
{ family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
|
|
15
16
|
{ family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
|
|
17
|
+
{ family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
|
|
16
18
|
{ family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
|
|
17
19
|
];
|
|
18
20
|
const FAMILY_SET = new Set(artifactFamilyCatalog.map((item) => item.family));
|
|
@@ -150,11 +152,7 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
150
152
|
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, String(error)));
|
|
151
153
|
return null;
|
|
152
154
|
}
|
|
153
|
-
const
|
|
154
|
-
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
155
|
-
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
156
|
-
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
|
|
157
|
-
const parsed = candidates.map((name) => parseArtifactName(name)).find((item) => item?.family === expectedFamily);
|
|
155
|
+
const parsed = parseReviewedInputReference(content, expectedFamily);
|
|
158
156
|
if (!parsed) {
|
|
159
157
|
diagnostics.push(diagnostic('BROKEN_REFERENCE', review.family, review.name, `${review.name} does not reference a canonical ${expectedFamily} artifact`));
|
|
160
158
|
return null;
|
|
@@ -164,9 +162,17 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
164
162
|
const stat = fs.lstatSync(abs);
|
|
165
163
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
166
164
|
throw new Error('referenced input is not a regular file');
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
165
|
+
const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
166
|
+
const receipt = receiptForOutput(taskContent, review.name);
|
|
167
|
+
if (!receipt)
|
|
168
|
+
throw new Error(`receipt for ${review.name} is missing`);
|
|
169
|
+
const expectedEvent = reviewEventName(review.family);
|
|
170
|
+
if (receipt.event !== expectedEvent || receipt.input !== parsed.name) {
|
|
171
|
+
throw new Error(`receipt for ${review.name} does not match ${parsed.name}`);
|
|
172
|
+
}
|
|
173
|
+
const actualSha256 = sha256File(abs);
|
|
174
|
+
if (actualSha256 !== receipt.inputSha256) {
|
|
175
|
+
throw new Error(`${parsed.name} content digest does not match receipt`);
|
|
170
176
|
}
|
|
171
177
|
return { ...parsed, path: abs, size: stat.size, mtimeMs: stat.mtimeMs };
|
|
172
178
|
}
|
|
@@ -175,6 +181,64 @@ function resolveReviewedInput(taskDir, review, expectedFamily, diagnostics) {
|
|
|
175
181
|
return null;
|
|
176
182
|
}
|
|
177
183
|
}
|
|
184
|
+
function parseReviewedInputReference(content, expectedFamily) {
|
|
185
|
+
const lines = content.split(/\r?\n/);
|
|
186
|
+
const header = lines.findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
187
|
+
const referenceBlock = header >= 0 ? lines.slice(header, header + 12).join('\n') : '';
|
|
188
|
+
const candidates = [...referenceBlock.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
|
|
189
|
+
return candidates
|
|
190
|
+
.map((name) => parseArtifactName(name))
|
|
191
|
+
.find((item) => item?.family === expectedFamily) ?? null;
|
|
192
|
+
}
|
|
193
|
+
function parseCodePlanInputReference(content) {
|
|
194
|
+
const lines = content.split(/\r?\n/);
|
|
195
|
+
const sectionStart = lines.findIndex((line) => /^##\s+(?:实现输入|Implementation Input)\s*$/.test(line.trim()));
|
|
196
|
+
if (sectionStart < 0)
|
|
197
|
+
return null;
|
|
198
|
+
const bodyLines = lines.slice(sectionStart + 1);
|
|
199
|
+
const nextSection = bodyLines.findIndex((line) => /^##\s+/.test(line.trim()));
|
|
200
|
+
const body = (nextSection < 0 ? bodyLines : bodyLines.slice(0, nextSection)).join('\n');
|
|
201
|
+
const match = body.match(/\*\*(?:方案输入|Plan Input)\*\*[::]\s*`([^`]+\.md)`/);
|
|
202
|
+
if (!match)
|
|
203
|
+
return null;
|
|
204
|
+
const parsed = parseArtifactName(match[1]);
|
|
205
|
+
return parsed?.family === 'plan' ? { ...parsed, family: 'plan' } : null;
|
|
206
|
+
}
|
|
207
|
+
function reviewEventName(family) {
|
|
208
|
+
if (family === 'review-analysis')
|
|
209
|
+
return 'review-analysis.completed';
|
|
210
|
+
if (family === 'review-plan')
|
|
211
|
+
return 'review-plan.completed';
|
|
212
|
+
if (family === 'review-code')
|
|
213
|
+
return 'review-code.completed';
|
|
214
|
+
throw new Error(`unsupported review family '${family}'`);
|
|
215
|
+
}
|
|
216
|
+
function resolveCodeInputReceipt(taskDir, code) {
|
|
217
|
+
let taskContent;
|
|
218
|
+
try {
|
|
219
|
+
taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
let receipt;
|
|
225
|
+
try {
|
|
226
|
+
receipt = receiptForOutput(taskContent, code.name);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
if (!receipt || receipt.event !== 'code.completed')
|
|
232
|
+
return null;
|
|
233
|
+
const plan = inspectArtifactDirectory(taskDir, 'plan');
|
|
234
|
+
if (plan.status !== 'ready')
|
|
235
|
+
return null;
|
|
236
|
+
const input = plan.artifacts.find((artifact) => artifact.name === receipt.input);
|
|
237
|
+
return input ? { input, inputSha256: receipt.inputSha256 } : null;
|
|
238
|
+
}
|
|
239
|
+
function resolveCodePlanInput(taskDir, code) {
|
|
240
|
+
return resolveCodeInputReceipt(taskDir, code);
|
|
241
|
+
}
|
|
178
242
|
function assertWritableInventory(inventory) {
|
|
179
243
|
if (inventory.status === 'failed')
|
|
180
244
|
return inventory.error;
|
|
@@ -191,7 +255,8 @@ const OPTIONAL_CONTEXT = {
|
|
|
191
255
|
analysis: { family: 'review-analysis', requireReference: true },
|
|
192
256
|
plan: { family: 'review-plan', requireReference: true },
|
|
193
257
|
'review-code': { family: 'review-plan' },
|
|
194
|
-
'manual-validation': { family: 'review-code' }
|
|
258
|
+
'manual-validation': { family: 'review-code' },
|
|
259
|
+
'validation-run': { family: 'review-code' }
|
|
195
260
|
};
|
|
196
261
|
function resolveArtifactContext(taskRef, family, options = {}) {
|
|
197
262
|
const inventory = inspectTaskArtifacts(taskRef, family, options);
|
|
@@ -246,10 +311,18 @@ function resolveCodeContext(inventory, options) {
|
|
|
246
311
|
}
|
|
247
312
|
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, null, null, 'No prior code artifact. Starting initial implementation (round 1 -> code.md).');
|
|
248
313
|
}
|
|
314
|
+
const codePlanInput = resolveCodePlanInput(inventory.taskDir, latestCode);
|
|
315
|
+
if (!codePlanInput) {
|
|
316
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `code completion receipt for ${latestCode.name} is missing or does not match the current plan`, latestCode.name);
|
|
317
|
+
}
|
|
318
|
+
if (reviewPlan.latest && !reviewPlan.reviewedInput) {
|
|
319
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `${reviewPlan.latest.name} has no valid reviewed input`, reviewPlan.latest.name);
|
|
320
|
+
}
|
|
249
321
|
if (reviewPlan.latest && reviewPlan.reviewedInput?.name === plan.latest.name) {
|
|
250
322
|
const verdict = parseVerdict(reviewPlan.latest.path);
|
|
251
|
-
|
|
252
|
-
|
|
323
|
+
const reviewedPlanSha256 = sha256File(reviewPlan.reviewedInput.path);
|
|
324
|
+
if (verdict.ok && verdict.verdict === 'Approved' && (codePlanInput.input.name !== plan.latest.name || codePlanInput.inputSha256 !== reviewedPlanSha256)) {
|
|
325
|
+
return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, verdict.verdict, reviewPlan.latest.name, `Latest ${reviewPlan.latest.name} approves plan content not captured by the latest code input receipt. Entering replan-driven init.`);
|
|
253
326
|
}
|
|
254
327
|
}
|
|
255
328
|
if (reviewMax < codeMax) {
|
|
@@ -306,12 +379,11 @@ function resolveDecisionImplementationInput(inventory, review) {
|
|
|
306
379
|
const activity = locateActivityLog(content);
|
|
307
380
|
if (!activity)
|
|
308
381
|
return { error: 'task has no unique Activity Log section' };
|
|
309
|
-
const
|
|
310
|
-
const completed = reviewEntries.at(-1);
|
|
382
|
+
const completed = activity.entries.some((entry) => /^Review Code \(Round \d+\)$/.test(entry.step) && entry.note.includes(`→ ${review.name}`));
|
|
311
383
|
if (!completed)
|
|
312
384
|
return { error: `cannot find completed Activity Log identity for ${review.name}` };
|
|
313
385
|
try {
|
|
314
|
-
return { input: selectPendingImplementationInput(rows
|
|
386
|
+
return { input: selectPendingImplementationInput(rows) };
|
|
315
387
|
}
|
|
316
388
|
catch (error) {
|
|
317
389
|
return { error: error instanceof Error ? error.message : String(error) };
|
|
@@ -376,5 +448,5 @@ function buildArtifactLinkSection(content, artifact) {
|
|
|
376
448
|
body = lines.join('\n').replace(/^\n+|\n+$/g, '');
|
|
377
449
|
return { aliases, heading, body };
|
|
378
450
|
}
|
|
379
|
-
export { artifactFamilyCatalog, artifactName, parseArtifactName, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
451
|
+
export { artifactFamilyCatalog, artifactName, parseArtifactName, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, parseReviewedInputReference, parseCodePlanInputReference, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
380
452
|
//# sourceMappingURL=artifact-lifecycle.js.map
|