@zq-silk/yui 2.23.0 → 2.25.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 +9 -4
- package/dist/artifacts/artifactCapability.js +6 -6
- package/dist/artifacts/gitArtifactRef.js +10 -10
- package/dist/artifacts/jobArtifact.js +5 -5
- package/dist/artifacts/managedGit.js +15 -12
- package/dist/artifacts/taskArtifactRepository.js +35 -33
- package/dist/cli.js +9 -2
- package/dist/commands/configCommands.js +64 -1
- package/dist/commands/projectCommands.js +9 -9
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskIntegrationCommands.js +1 -1
- package/dist/commands/taskPublicationAdoptCommand.js +1 -1
- package/dist/commands/taskUpstreamCommands.js +1 -1
- package/dist/config/configCatalog.js +3 -0
- package/dist/config/gitConfig.js +20 -0
- package/dist/config/yuiConfig.js +1 -0
- package/dist/controlPlaneCli.js +4 -4
- package/dist/controller/jobArtifactControl.js +3 -3
- package/dist/controller/jobDefinitionControl.js +1 -1
- package/dist/integration/gitIntegrationService.js +1 -1
- package/dist/kernel/builtinCapabilities.js +3 -3
- package/dist/repository/gitCommand.js +153 -0
- package/dist/repository/gitCompatibility.js +3 -2
- package/dist/repository/gitWorkspace.js +579 -563
- package/dist/repository/gitWorktreeInventory.js +15 -19
- package/dist/repository/projectRefresh.js +1 -1
- package/dist/repository/taskBaseFreshness.js +1 -4
- package/dist/repository/taskWorkspacePreparer.js +1 -1
- package/dist/runtime/nativeExecutable.js +5 -2
- package/dist/runtime/platform.js +11 -0
- package/dist/storage/sqliteSchema.js +10 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/taskStore.js +7 -1
- package/dist/web/webConversation.js +2 -2
- package/dist/web/webMaterials.js +19 -19
- package/dist/web/webTaskSurface.js +3 -3
- package/dist/workspace/workItemChangeSetManager.js +3 -3
- package/docs/project-refresh.md +3 -2
- package/docs/project-refresh.zh-CN.md +2 -1
- package/docs/release-workflow.md +58 -2
- package/docs/release-workflow.zh-CN.md +45 -1
- package/docs/roles-and-configuration.md +34 -0
- package/docs/roles-and-configuration.zh-CN.md +27 -0
- package/docs/storage-baseline.md +5 -1
- package/docs/storage-baseline.zh-CN.md +3 -1
- package/docs/testing/verification-levels.md +7 -7
- package/docs/testing/verification-levels.zh-CN.md +6 -6
- package/i18n/README.zh-CN.md +8 -4
- package/package.json +1 -1
- package/dist/runtime/native/darwin-x64/claude-process-owner +0 -0
- package/dist/runtime/native/darwin-x64/process-identity +0 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/zhangqian-silk/yui/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|

|
|
8
|
-

|
|
9
9
|
[](#contributing)
|
|
10
10
|
|
|
11
11
|
Give your Agents work to carry forward, not just another chat to answer.
|
|
@@ -46,15 +46,20 @@ For the self-built coding Agent (without a Controller or Yui Home), see
|
|
|
46
46
|
[`yui agent`](docs/native-agent.md). Its explicit configuration and product state
|
|
47
47
|
are separate from the orchestration setup below.
|
|
48
48
|
|
|
49
|
-
You need Linux x64 (glibc) or macOS
|
|
49
|
+
You need Linux x64 (glibc) or macOS Apple Silicon with native arm64 Node.js, Git 2.29.0 or newer, tmux, and
|
|
50
50
|
Node.js `^20.17.0`, `^22.9.0`, `^24.0.0` or `^26.0.0`. On macOS install tmux with
|
|
51
51
|
`brew install tmux`. For the simplest setup, have Codex CLI or Claude Code CLI
|
|
52
52
|
installed and ready to use with your own account. Yui coordinates those
|
|
53
53
|
Agents; it does not supply model access.
|
|
54
54
|
|
|
55
|
-
The npm package includes prebuilt Yui helpers for Linux x64
|
|
56
|
-
Apple Silicon. The same package is tested on
|
|
55
|
+
The npm package includes prebuilt Yui helpers for Linux x64 and macOS
|
|
56
|
+
Apple Silicon. The same package is tested on both platforms before
|
|
57
57
|
publication; Yui selects only the helper for the running platform.
|
|
58
|
+
Starting with 2.25.0, macOS Intel and x64 Node.js under Rosetta are unsupported.
|
|
59
|
+
Intel Mac users must stay on 2.23.0; Apple Silicon users must switch to native
|
|
60
|
+
arm64 Node.js. npm's OS/CPU fields cannot express these platform pairs, so npm
|
|
61
|
+
installation may succeed on an unsupported combination, but Yui rejects it
|
|
62
|
+
before loading either CLI product or opening the Home. No install scripts are added.
|
|
58
63
|
|
|
59
64
|
For Claude, Yui passes through your authentication environment and native
|
|
60
65
|
configuration directory; Claude selects the API key or login method using its
|
|
@@ -32,13 +32,13 @@ function requireTextContent(content) {
|
|
|
32
32
|
* `artifact.save`: write `content` to `relativePath` in the Task's local repo,
|
|
33
33
|
* commit exactly that path, and return the resulting commit-pinned reference.
|
|
34
34
|
*/
|
|
35
|
-
export async function saveArtifactCapability(home, taskId, input) {
|
|
35
|
+
export async function saveArtifactCapability(home, taskId, input, getConfig) {
|
|
36
36
|
const bytes = artifactInputBytes(input);
|
|
37
37
|
return saveArtifactFile(home, taskId, {
|
|
38
38
|
relativePath: input.relativePath, bytes,
|
|
39
39
|
...(input.message === undefined ? {} : { message: input.message }),
|
|
40
40
|
...(input.expectedHead === undefined ? {} : { expectedHead: input.expectedHead })
|
|
41
|
-
});
|
|
41
|
+
}, getConfig);
|
|
42
42
|
}
|
|
43
43
|
/** Shared bounded text/static-image input; never executes HTML or scripts. */
|
|
44
44
|
export function artifactInputBytes(input) {
|
|
@@ -66,8 +66,8 @@ export function artifactInputBytes(input) {
|
|
|
66
66
|
* frozen evidence. Images return explicit base64 and bounded raster metadata;
|
|
67
67
|
* text returns UTF-8. Both carry the resolved commit and exact digest.
|
|
68
68
|
*/
|
|
69
|
-
export async function readArtifactCapability(home, taskId, input) {
|
|
70
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
69
|
+
export async function readArtifactCapability(home, taskId, input, getConfig) {
|
|
70
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
71
71
|
if (!repo.exists())
|
|
72
72
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
73
73
|
const safeRelative = safeRelativeArtifactPath(input.relativePath);
|
|
@@ -101,8 +101,8 @@ export function presentArtifactContent(content) {
|
|
|
101
101
|
* `artifact.list`: tracked artifacts at HEAD (empty when the Task has no repo
|
|
102
102
|
* yet). This is an ordinary current read; it does not freeze anything.
|
|
103
103
|
*/
|
|
104
|
-
export async function listArtifactsCapability(home, taskId) {
|
|
105
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
104
|
+
export async function listArtifactsCapability(home, taskId, getConfig) {
|
|
105
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
106
106
|
if (!repo.exists())
|
|
107
107
|
return [];
|
|
108
108
|
return repo.list();
|
|
@@ -67,9 +67,9 @@ export function parseGitArtifactRef(value, taskId) {
|
|
|
67
67
|
* though for a correctly pinned commit this can only happen if the recorded
|
|
68
68
|
* digest was wrong, since the commit freezes the bytes.
|
|
69
69
|
*/
|
|
70
|
-
export async function resolveGitArtifact(home, ref) {
|
|
70
|
+
export async function resolveGitArtifact(home, ref, getConfig) {
|
|
71
71
|
const valid = validateGitArtifactRef(ref);
|
|
72
|
-
const repo = openTaskArtifactRepository(home, valid.taskId);
|
|
72
|
+
const repo = openTaskArtifactRepository(home, valid.taskId, getConfig);
|
|
73
73
|
if (!repo.exists()) {
|
|
74
74
|
throw new Error(`Artifact repository is unavailable for ${valid.taskId}.`);
|
|
75
75
|
}
|
|
@@ -88,8 +88,8 @@ export async function resolveGitArtifact(home, ref) {
|
|
|
88
88
|
* for the resulting state. The returned reference is self-certifying, so a
|
|
89
89
|
* caller can record it directly as frozen evidence.
|
|
90
90
|
*/
|
|
91
|
-
export async function saveArtifactFile(home, taskId, input) {
|
|
92
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
91
|
+
export async function saveArtifactFile(home, taskId, input, getConfig) {
|
|
92
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
93
93
|
const relativePath = safeRelativeArtifactPath(input.relativePath);
|
|
94
94
|
const result = await repo.save({
|
|
95
95
|
files: [{ relativePath, bytes: input.bytes }],
|
|
@@ -105,15 +105,15 @@ export async function saveArtifactFile(home, taskId, input) {
|
|
|
105
105
|
digest: content.digest
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
-
export async function readCurrentArtifact(home, taskId, relativePath) {
|
|
109
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
108
|
+
export async function readCurrentArtifact(home, taskId, relativePath, getConfig) {
|
|
109
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
110
110
|
if (!repo.exists())
|
|
111
111
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
112
112
|
return repo.read(safeRelativeArtifactPath(relativePath));
|
|
113
113
|
}
|
|
114
114
|
/** Ordinary listing: tracked artifacts at HEAD (ASYNC, not frozen). Empty when no repo yet. */
|
|
115
|
-
export async function listCurrentArtifacts(home, taskId) {
|
|
116
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
115
|
+
export async function listCurrentArtifacts(home, taskId, getConfig) {
|
|
116
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
117
117
|
if (!repo.exists())
|
|
118
118
|
return [];
|
|
119
119
|
return repo.list();
|
|
@@ -123,8 +123,8 @@ export async function listCurrentArtifacts(home, taskId) {
|
|
|
123
123
|
* references (ASYNC). Used at Candidate submission / completion time to capture
|
|
124
124
|
* exactly what exists now. Fails if any path is not tracked at HEAD.
|
|
125
125
|
*/
|
|
126
|
-
export async function pinCurrentArtifacts(home, taskId, relativePaths) {
|
|
127
|
-
const repo = openTaskArtifactRepository(home, taskId);
|
|
126
|
+
export async function pinCurrentArtifacts(home, taskId, relativePaths, getConfig) {
|
|
127
|
+
const repo = openTaskArtifactRepository(home, taskId, getConfig);
|
|
128
128
|
if (!repo.exists())
|
|
129
129
|
throw new Error(`Artifact repository is unavailable for ${taskId}.`);
|
|
130
130
|
const head = await repo.head();
|
|
@@ -57,7 +57,7 @@ export function jobArtifactOwner(run) {
|
|
|
57
57
|
}
|
|
58
58
|
/** Each Run has a distinct working tree and commit fence even for identical paths.
|
|
59
59
|
* The retained storage is unrelated to execution workspaces/logs/checkpoints. */
|
|
60
|
-
export function openJobArtifactRepository(home, owner, sourceRun) {
|
|
60
|
+
export function openJobArtifactRepository(home, owner, sourceRun, getConfig) {
|
|
61
61
|
validateScope(owner.scope);
|
|
62
62
|
validateScope(sourceRun.scope);
|
|
63
63
|
if (!isDeepStrictEqual(owner.scope, sourceRun.scope))
|
|
@@ -65,13 +65,13 @@ export function openJobArtifactRepository(home, owner, sourceRun) {
|
|
|
65
65
|
const scopePath = owner.scope.kind === "global" ? ["global"] : ["task", owner.scope.taskId];
|
|
66
66
|
const path = join(home, "job-artifacts", ...scopePath, identity(owner.jobId), identity(sourceRun.runId));
|
|
67
67
|
const lockKey = `job-${createHash("sha256").update(JSON.stringify([jobScopeKey(owner.scope), owner.jobId, sourceRun.runId])).digest("hex")}`;
|
|
68
|
-
return openVersionedArtifactRepository(home, lockKey, path);
|
|
68
|
+
return openVersionedArtifactRepository(home, lockKey, path, getConfig);
|
|
69
69
|
}
|
|
70
|
-
export async function readJobArtifactReference(home, ref) {
|
|
70
|
+
export async function readJobArtifactReference(home, ref, getConfig) {
|
|
71
71
|
validateJobArtifactReference(ref);
|
|
72
72
|
const repo = ref.owner.kind === "task"
|
|
73
|
-
? openTaskArtifactRepository(home, ref.owner.taskId)
|
|
74
|
-
: openJobArtifactRepository(home, ref.owner, ref.sourceRun);
|
|
73
|
+
? openTaskArtifactRepository(home, ref.owner.taskId, getConfig)
|
|
74
|
+
: openJobArtifactRepository(home, ref.owner, ref.sourceRun, getConfig);
|
|
75
75
|
const content = await repo.read(ref.relativePath, ref.version);
|
|
76
76
|
if (content.digest !== ref.digest)
|
|
77
77
|
throw new Error("Artifact digest mismatch; preserved evidence was not replaced.");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { promisify } from "node:util";
|
|
3
1
|
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
4
|
-
|
|
2
|
+
import { executeGitCommand, GitCommandError, LOCAL_GIT_TIMEOUT_MS } from "../repository/gitCommand.js";
|
|
3
|
+
import { resolveGitLocalTimeoutSeconds } from "../config/gitConfig.js";
|
|
5
4
|
/**
|
|
6
5
|
* Hardened, isolated Git runner for per-Task artifact repositories.
|
|
7
6
|
*
|
|
@@ -65,7 +64,9 @@ export class ManagedGitError extends Error {
|
|
|
65
64
|
stderr;
|
|
66
65
|
args;
|
|
67
66
|
constructor(args, stderr, cause) {
|
|
68
|
-
super(
|
|
67
|
+
super(cause instanceof Error
|
|
68
|
+
? cause.message.replace(/^Git command/u, "Managed git command")
|
|
69
|
+
: stderr.length === 0 ? "Managed git command failed." : `Managed git command failed: ${stderr}`, { cause });
|
|
69
70
|
this.name = "ManagedGitError";
|
|
70
71
|
this.stderr = stderr;
|
|
71
72
|
this.args = args;
|
|
@@ -124,14 +125,14 @@ function assertSafeArguments(args) {
|
|
|
124
125
|
*/
|
|
125
126
|
async function spawnManagedGit(repoPath, args, options) {
|
|
126
127
|
assertSafeArguments(args);
|
|
127
|
-
const
|
|
128
|
+
const timeoutMs = options?.timeoutMs ?? (options?.getConfig === undefined
|
|
129
|
+
? LOCAL_GIT_TIMEOUT_MS : resolveGitLocalTimeoutSeconds(options.getConfig().gitLocalTimeoutSeconds) * 1_000);
|
|
130
|
+
const program = supportedGit(timeoutMs);
|
|
128
131
|
try {
|
|
129
|
-
const result = await
|
|
130
|
-
|
|
131
|
-
env: managedGitEnvironment(),
|
|
132
|
+
const result = await executeGitCommand(program.path, [...HARDENING_FLAGS, "-C", repoPath, ...args], {
|
|
133
|
+
environment: managedGitEnvironment(),
|
|
132
134
|
maxBuffer: options?.maxBuffer ?? 16 * 1024 * 1024,
|
|
133
|
-
|
|
134
|
-
windowsHide: true
|
|
135
|
+
budget: { totalMs: timeoutMs }
|
|
135
136
|
});
|
|
136
137
|
return { stdout: result.stdout, stderr: result.stderr };
|
|
137
138
|
}
|
|
@@ -160,14 +161,16 @@ export async function managedGitBuffer(repoPath, args, options) {
|
|
|
160
161
|
const { stdout } = await spawnManagedGit(repoPath, args, options);
|
|
161
162
|
return stdout;
|
|
162
163
|
}
|
|
163
|
-
/**
|
|
164
|
+
/** Return false for a completed non-zero exit, but preserve timeouts/process failures. */
|
|
164
165
|
export async function managedGitSucceeds(repoPath, args, options) {
|
|
165
166
|
try {
|
|
166
167
|
await spawnManagedGit(repoPath, args, options);
|
|
167
168
|
return true;
|
|
168
169
|
}
|
|
169
170
|
catch (error) {
|
|
170
|
-
if (error instanceof ManagedGitError
|
|
171
|
+
if (error instanceof ManagedGitError && error.cause instanceof GitCommandError
|
|
172
|
+
&& error.cause.timeoutKind === undefined && error.cause.signal === null
|
|
173
|
+
&& typeof error.cause.code === "number")
|
|
171
174
|
return false;
|
|
172
175
|
throw error;
|
|
173
176
|
}
|
|
@@ -3,6 +3,7 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import { lstat, mkdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, join, relative } from "node:path";
|
|
5
5
|
import { supportedGit } from "../repository/gitCompatibility.js";
|
|
6
|
+
import { resolveGitLocalTimeoutSeconds } from "../config/gitConfig.js";
|
|
6
7
|
import { acquireArtifactCommitLock } from "./artifactCommitLock.js";
|
|
7
8
|
import { resolveContainedArtifactPath, safeRelativeArtifactPath, taskArtifactRepoPath } from "./artifactPaths.js";
|
|
8
9
|
import { ManagedGitError, externalProgramConfigViolations, managedGit, managedGitBuffer, managedGitSucceeds, requireCommitId } from "./managedGit.js";
|
|
@@ -88,14 +89,14 @@ export class ArtifactHeadConflictError extends Error {
|
|
|
88
89
|
* (e.g. `store.rootDirectory()`), so ownership is derived from the Task, never
|
|
89
90
|
* from ambient state.
|
|
90
91
|
*/
|
|
91
|
-
export function openTaskArtifactRepository(home, taskId) {
|
|
92
|
+
export function openTaskArtifactRepository(home, taskId, getConfig) {
|
|
92
93
|
const repoPath = taskArtifactRepoPath(home, taskId);
|
|
93
|
-
const repo = openVersionedArtifactRepository(home, taskId, repoPath);
|
|
94
|
+
const repo = openVersionedArtifactRepository(home, taskId, repoPath, getConfig);
|
|
94
95
|
return { ...repo, taskId, save: async (input) => ({ ...await repo.save(input), taskId }) };
|
|
95
96
|
}
|
|
96
97
|
/** Shared local-only storage. Callers derive paths from validated owning identities,
|
|
97
98
|
* never from a user-supplied filesystem location. Task layout/commits stay intact. */
|
|
98
|
-
export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
99
|
+
export function openVersionedArtifactRepository(home, key, repoPath, getConfig) {
|
|
99
100
|
const taskId = key; // Existing error fields retain their public spelling.
|
|
100
101
|
const checkBoundary = async () => {
|
|
101
102
|
await resolveContainedArtifactPath(home, `${relative(home, repoPath).split("\\").join("/")}/boundary-check`);
|
|
@@ -115,21 +116,21 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
115
116
|
await checkBoundary();
|
|
116
117
|
if (!exists())
|
|
117
118
|
return null;
|
|
118
|
-
return currentHead(repoPath);
|
|
119
|
+
return currentHead(repoPath, getConfig);
|
|
119
120
|
};
|
|
120
121
|
const ensureUnlocked = async () => {
|
|
121
122
|
await checkBoundary();
|
|
122
|
-
supportedGit();
|
|
123
|
+
supportedGit(resolveGitLocalTimeoutSeconds(getConfig?.().gitLocalTimeoutSeconds) * 1_000);
|
|
123
124
|
if (exists()) {
|
|
124
|
-
await assertNoRemote(taskId, repoPath);
|
|
125
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
127
128
|
await mkdir(repoPath, { recursive: true, mode: 0o700 });
|
|
128
|
-
await managedGit(repoPath, ["init", "-b", ARTIFACT_BRANCH]);
|
|
129
|
+
await managedGit(repoPath, ["init", "-b", ARTIFACT_BRANCH], { getConfig });
|
|
129
130
|
// An empty root commit guarantees HEAD exists, so scoped per-path commits
|
|
130
131
|
// and expected-HEAD checks work uniformly from the very first save.
|
|
131
|
-
await managedGit(repoPath, ["commit", "--allow-empty", "-m", "init artifact repo"]);
|
|
132
|
-
await assertNoRemote(taskId, repoPath);
|
|
132
|
+
await managedGit(repoPath, ["commit", "--allow-empty", "-m", "init artifact repo"], { getConfig });
|
|
133
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
133
134
|
};
|
|
134
135
|
const ensure = async () => {
|
|
135
136
|
const release = await acquireArtifactCommitLock(home, key);
|
|
@@ -169,10 +170,10 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
169
170
|
const release = await acquireArtifactCommitLock(home, key);
|
|
170
171
|
try {
|
|
171
172
|
await ensureUnlocked();
|
|
172
|
-
await assertNoRemote(taskId, repoPath);
|
|
173
|
+
await assertNoRemote(taskId, repoPath, getConfig);
|
|
173
174
|
// Expected-HEAD check under the lock: a conflict stops without writing.
|
|
174
175
|
if (input.expectedHead !== undefined) {
|
|
175
|
-
const actual = await currentHead(repoPath);
|
|
176
|
+
const actual = await currentHead(repoPath, getConfig);
|
|
176
177
|
if (actual !== input.expectedHead) {
|
|
177
178
|
throw new ArtifactHeadConflictError(taskId, input.expectedHead, actual);
|
|
178
179
|
}
|
|
@@ -186,17 +187,17 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
186
187
|
}
|
|
187
188
|
const pathspecs = prepared.map((file) => file.relativePath);
|
|
188
189
|
// Stage exactly the saved paths so new files become known to Git...
|
|
189
|
-
await managedGit(repoPath, ["add", "--", ...pathspecs]);
|
|
190
|
+
await managedGit(repoPath, ["add", "--", ...pathspecs], { getConfig });
|
|
190
191
|
// ...but if nothing actually changed, do not create an empty commit.
|
|
191
192
|
const noChanges = await managedGitSucceeds(repoPath, [
|
|
192
193
|
"diff", "--cached", "--quiet", "--", ...pathspecs
|
|
193
|
-
]);
|
|
194
|
+
], { getConfig });
|
|
194
195
|
if (noChanges) {
|
|
195
|
-
return { commit: await currentHead(repoPath), savedPaths: pathspecs };
|
|
196
|
+
return { commit: await currentHead(repoPath, getConfig), savedPaths: pathspecs };
|
|
196
197
|
}
|
|
197
198
|
// `--only <paths>` records exactly these paths; any other WIP stays WIP.
|
|
198
|
-
await managedGit(repoPath, ["commit", "--only", "-m", message, "--", ...pathspecs]);
|
|
199
|
-
return { commit: await currentHead(repoPath), savedPaths: pathspecs };
|
|
199
|
+
await managedGit(repoPath, ["commit", "--only", "-m", message, "--", ...pathspecs], { getConfig });
|
|
200
|
+
return { commit: await currentHead(repoPath, getConfig), savedPaths: pathspecs };
|
|
200
201
|
}
|
|
201
202
|
finally {
|
|
202
203
|
release();
|
|
@@ -208,30 +209,31 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
208
209
|
// A pinned commit reads frozen evidence; otherwise read the current HEAD.
|
|
209
210
|
// Both resolve `<commit>:<path>`, an OBJECT spec (never the working tree, so
|
|
210
211
|
// a read never follows a symlink or sees unrelated WIP).
|
|
211
|
-
const pinned = await artifactCommit(repoPath, commit);
|
|
212
|
+
const pinned = await artifactCommit(repoPath, commit, getConfig);
|
|
212
213
|
const objectSpec = `${pinned}:${safeRelative}`;
|
|
213
214
|
// `<commit>:<path>` for a DIRECTORY resolves to a tree, and `git show` would
|
|
214
215
|
// print a tree listing rather than fail — silently returning directory
|
|
215
216
|
// metadata as if it were file bytes. Verify the object is a blob first, so a
|
|
216
217
|
// read only ever yields real file content.
|
|
217
|
-
const objectType = (await managedGit(repoPath, ["cat-file", "-t", objectSpec]).catch((error) => {
|
|
218
|
+
const objectType = (await managedGit(repoPath, ["cat-file", "-t", objectSpec], { getConfig }).catch((error) => {
|
|
218
219
|
if (error instanceof ManagedGitError) {
|
|
219
|
-
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}
|
|
220
|
+
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}: ${error.message}`, { cause: error });
|
|
220
221
|
}
|
|
221
222
|
throw error;
|
|
222
223
|
})).trim();
|
|
223
224
|
if (objectType !== "blob") {
|
|
224
225
|
throw new Error(`Artifact ${safeRelative} at ${pinned} is not a file (${objectType}).`);
|
|
225
226
|
}
|
|
226
|
-
const size = Number((await managedGit(repoPath, ["cat-file", "-s", objectSpec])).trim());
|
|
227
|
+
const size = Number((await managedGit(repoPath, ["cat-file", "-s", objectSpec], { getConfig })).trim());
|
|
227
228
|
if (!Number.isSafeInteger(size) || size > MAX_ARTIFACT_BYTES) {
|
|
228
229
|
throw new Error(`Artifact ${safeRelative} exceeds the ${MAX_ARTIFACT_BYTES}-byte limit.`);
|
|
229
230
|
}
|
|
230
231
|
const bytes = await managedGitBuffer(repoPath, ["cat-file", "blob", objectSpec], {
|
|
232
|
+
getConfig,
|
|
231
233
|
maxBuffer: MAX_ARTIFACT_BYTES + 4096
|
|
232
234
|
}).catch((error) => {
|
|
233
235
|
if (error instanceof ManagedGitError) {
|
|
234
|
-
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}
|
|
236
|
+
throw new Error(`Artifact ${safeRelative} is unavailable at ${pinned}: ${error.message}`, { cause: error });
|
|
235
237
|
}
|
|
236
238
|
throw error;
|
|
237
239
|
});
|
|
@@ -246,10 +248,10 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
246
248
|
await checkBoundary();
|
|
247
249
|
if (!exists())
|
|
248
250
|
return [];
|
|
249
|
-
const pinned = await artifactCommit(repoPath, commit);
|
|
251
|
+
const pinned = await artifactCommit(repoPath, commit, getConfig);
|
|
250
252
|
const output = await managedGit(repoPath, [
|
|
251
253
|
"ls-tree", "-r", "-z", "--long", "--full-tree", pinned
|
|
252
|
-
]);
|
|
254
|
+
], { getConfig });
|
|
253
255
|
const entries = [];
|
|
254
256
|
for (const record of output.split("\0").filter(Boolean)) {
|
|
255
257
|
// Format: "<mode> <type> <object> <size>\t<path>"
|
|
@@ -271,7 +273,7 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
271
273
|
return [];
|
|
272
274
|
const output = await managedGit(repoPath, [
|
|
273
275
|
"status", "--porcelain=v1", "--untracked-files=all", "-z"
|
|
274
|
-
]);
|
|
276
|
+
], { getConfig });
|
|
275
277
|
// Porcelain v1 -z records: "XY <path>\0" (rename adds a second \0 field).
|
|
276
278
|
const changes = [];
|
|
277
279
|
for (const record of output.split("\0").filter(Boolean)) {
|
|
@@ -283,16 +285,16 @@ export function openVersionedArtifactRepository(home, key, repoPath) {
|
|
|
283
285
|
};
|
|
284
286
|
return { repoPath, ensure, exists, head, save, read, list, workingChanges };
|
|
285
287
|
}
|
|
286
|
-
async function currentHead(repoPath) {
|
|
287
|
-
return requireCommitId(await managedGit(repoPath, ["rev-parse", "HEAD^{commit}"]));
|
|
288
|
+
async function currentHead(repoPath, getConfig) {
|
|
289
|
+
return requireCommitId(await managedGit(repoPath, ["rev-parse", "HEAD^{commit}"], { getConfig }));
|
|
288
290
|
}
|
|
289
|
-
async function artifactCommit(repoPath, value) {
|
|
291
|
+
async function artifactCommit(repoPath, value, getConfig) {
|
|
290
292
|
if (value === undefined)
|
|
291
|
-
return currentHead(repoPath);
|
|
293
|
+
return currentHead(repoPath, getConfig);
|
|
292
294
|
const commit = requireCommitId(value);
|
|
293
|
-
const type = await managedGit(repoPath, ["cat-file", "-t", commit]).catch(error => {
|
|
295
|
+
const type = await managedGit(repoPath, ["cat-file", "-t", commit], { getConfig }).catch(error => {
|
|
294
296
|
if (error instanceof ManagedGitError)
|
|
295
|
-
throw new Error(`Artifact commit ${commit} is unavailable
|
|
297
|
+
throw new Error(`Artifact commit ${commit} is unavailable: ${error.message}`, { cause: error });
|
|
296
298
|
throw error;
|
|
297
299
|
});
|
|
298
300
|
if (type.trim() !== "commit") {
|
|
@@ -306,8 +308,8 @@ async function artifactCommit(repoPath, value) {
|
|
|
306
308
|
* program on add/diff/commit. Both are external violations — we report and stop,
|
|
307
309
|
* never silently remove them.
|
|
308
310
|
*/
|
|
309
|
-
async function assertNoRemote(taskId, repoPath) {
|
|
310
|
-
const output = await managedGit(repoPath, ["remote"]);
|
|
311
|
+
async function assertNoRemote(taskId, repoPath, getConfig) {
|
|
312
|
+
const output = await managedGit(repoPath, ["remote"], { getConfig });
|
|
311
313
|
const remotes = output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
312
314
|
if (remotes.length > 0) {
|
|
313
315
|
throw new ArtifactRemoteViolationError(taskId, remotes);
|
|
@@ -315,7 +317,7 @@ async function assertNoRemote(taskId, repoPath) {
|
|
|
315
317
|
// A repo's own `.git/config` is read even with global/system config disabled,
|
|
316
318
|
// so a committed `.gitattributes` filter paired with a repo-local
|
|
317
319
|
// `filter.<n>.clean = <cmd>` would run `<cmd>` on `git add`. Detect and stop.
|
|
318
|
-
const configListZ = await managedGit(repoPath, ["config", "--local", "--list", "-z"]);
|
|
320
|
+
const configListZ = await managedGit(repoPath, ["config", "--local", "--list", "-z"], { getConfig });
|
|
319
321
|
const violations = externalProgramConfigViolations(configListZ);
|
|
320
322
|
if (violations.length > 0) {
|
|
321
323
|
throw new ArtifactManagedConfigViolationError(taskId, violations);
|
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { unsupportedPlatformMessage } from "./runtime/platform.js";
|
|
2
3
|
// No static control-plane imports: the product never resolves/initializes Yui Home.
|
|
3
4
|
const arguments_ = process.argv.slice(2);
|
|
4
5
|
let commandOffset = 0;
|
|
5
6
|
while (arguments_[commandOffset] === '--json')
|
|
6
7
|
commandOffset++;
|
|
7
8
|
const commandArguments = arguments_.slice(commandOffset);
|
|
8
|
-
|
|
9
|
+
const platformFailure = unsupportedPlatformMessage();
|
|
10
|
+
if (platformFailure !== undefined) {
|
|
11
|
+
process.stderr.write(`${arguments_.includes("--json")
|
|
12
|
+
? JSON.stringify({ ok: false, code: "unsupported_platform", message: platformFailure })
|
|
13
|
+
: platformFailure}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
}
|
|
16
|
+
else if (commandArguments[0] === 'agent' || (commandArguments[0] === 'help' && commandArguments[1] === 'agent')) {
|
|
9
17
|
let outputFailed = false;
|
|
10
18
|
// Cover early help/config writes as well as execution UI disconnection.
|
|
11
19
|
process.stdout.on('error', () => { outputFailed = true; process.exitCode = 1; });
|
|
@@ -31,4 +39,3 @@ if (commandArguments[0] === 'agent' || (commandArguments[0] === 'help' && comman
|
|
|
31
39
|
else {
|
|
32
40
|
await import('./controlPlaneCli.js');
|
|
33
41
|
}
|
|
34
|
-
export {};
|
|
@@ -2,7 +2,7 @@ import { mkdirSync, realpathSync } from "node:fs";
|
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
|
|
4
4
|
import { resolveTimeZone } from "../config/timeZone.js";
|
|
5
|
-
import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
5
|
+
import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveDeliveryTimeoutSeconds, resolveGitLocalTimeoutSeconds, resolveGitRemoteInactivityTimeoutSeconds, resolveGitRemoteTimeoutSeconds, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
6
6
|
import { usageError } from "../errors/cliError.js";
|
|
7
7
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
8
8
|
import { REVIEW_TRIGGERS } from "../review/reviewConfig.js";
|
|
@@ -75,6 +75,9 @@ export function effectiveConfigData(config, domain) {
|
|
|
75
75
|
controllerTaskConcurrency: resolveControllerTaskConcurrency(config.controllerTaskConcurrency),
|
|
76
76
|
agentLaunchInactivityTimeoutSeconds: resolveAgentLaunchInactivityTimeoutSeconds(config.agentLaunchInactivityTimeoutSeconds),
|
|
77
77
|
deliveryTimeoutSeconds: resolveDeliveryTimeoutSeconds(config.deliveryTimeoutSeconds),
|
|
78
|
+
gitLocalTimeoutSeconds: resolveGitLocalTimeoutSeconds(config.gitLocalTimeoutSeconds),
|
|
79
|
+
gitRemoteInactivityTimeoutSeconds: resolveGitRemoteInactivityTimeoutSeconds(config.gitRemoteInactivityTimeoutSeconds),
|
|
80
|
+
gitRemoteTimeoutSeconds: resolveGitRemoteTimeoutSeconds(config.gitRemoteTimeoutSeconds),
|
|
78
81
|
tmuxBin: resolveTmuxBin(config.tmuxBin),
|
|
79
82
|
tmuxHistoryLimit: resolveTmuxHistoryLimit(config.tmuxHistoryLimit),
|
|
80
83
|
telemetryEnabled: resolveTelemetryEnabled(config.telemetryEnabled),
|
|
@@ -393,6 +396,66 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
393
396
|
return `Delivery timeout reset to ${DEFAULT_DELIVERY_TIMEOUT_SECONDS} seconds\n`;
|
|
394
397
|
}
|
|
395
398
|
},
|
|
399
|
+
{
|
|
400
|
+
key: "git-local-timeout-seconds",
|
|
401
|
+
showLabel: "Git local total timeout",
|
|
402
|
+
showValue: (config) => `${resolveGitLocalTimeoutSeconds(config.gitLocalTimeoutSeconds)} seconds`,
|
|
403
|
+
set(args, store) {
|
|
404
|
+
const usage = "Tools config set usage: yui config tools set git-local-timeout-seconds <1-86400>.";
|
|
405
|
+
if (args.length !== 1)
|
|
406
|
+
throw usageError(usage);
|
|
407
|
+
const gitLocalTimeoutSeconds = validatedConfigValue(() => resolveGitLocalTimeoutSeconds(Number(args[0])), usage);
|
|
408
|
+
saveConfigKey(store, (config) => ({ ...config, gitLocalTimeoutSeconds }));
|
|
409
|
+
return `Git local total timeout set to ${gitLocalTimeoutSeconds} seconds\n`;
|
|
410
|
+
},
|
|
411
|
+
clear(store) {
|
|
412
|
+
saveConfigKey(store, (config) => {
|
|
413
|
+
const { gitLocalTimeoutSeconds: _removed, ...rest } = config;
|
|
414
|
+
return rest;
|
|
415
|
+
});
|
|
416
|
+
return "Git local total timeout reset to default\n";
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
key: "git-remote-inactivity-timeout-seconds",
|
|
421
|
+
showLabel: "Git remote inactivity timeout",
|
|
422
|
+
showValue: (config) => `${resolveGitRemoteInactivityTimeoutSeconds(config.gitRemoteInactivityTimeoutSeconds)} seconds`,
|
|
423
|
+
set(args, store) {
|
|
424
|
+
const usage = "Tools config set usage: yui config tools set git-remote-inactivity-timeout-seconds <1-86400>.";
|
|
425
|
+
if (args.length !== 1)
|
|
426
|
+
throw usageError(usage);
|
|
427
|
+
const gitRemoteInactivityTimeoutSeconds = validatedConfigValue(() => resolveGitRemoteInactivityTimeoutSeconds(Number(args[0])), usage);
|
|
428
|
+
saveConfigKey(store, (config) => ({ ...config, gitRemoteInactivityTimeoutSeconds }));
|
|
429
|
+
return `Git remote inactivity timeout set to ${gitRemoteInactivityTimeoutSeconds} seconds\n`;
|
|
430
|
+
},
|
|
431
|
+
clear(store) {
|
|
432
|
+
saveConfigKey(store, (config) => {
|
|
433
|
+
const { gitRemoteInactivityTimeoutSeconds: _removed, ...rest } = config;
|
|
434
|
+
return rest;
|
|
435
|
+
});
|
|
436
|
+
return "Git remote inactivity timeout reset to default\n";
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
key: "git-remote-timeout-seconds",
|
|
441
|
+
showLabel: "Git remote total timeout",
|
|
442
|
+
showValue: (config) => `${resolveGitRemoteTimeoutSeconds(config.gitRemoteTimeoutSeconds)} seconds`,
|
|
443
|
+
set(args, store) {
|
|
444
|
+
const usage = "Tools config set usage: yui config tools set git-remote-timeout-seconds <1-86400>.";
|
|
445
|
+
if (args.length !== 1)
|
|
446
|
+
throw usageError(usage);
|
|
447
|
+
const gitRemoteTimeoutSeconds = validatedConfigValue(() => resolveGitRemoteTimeoutSeconds(Number(args[0])), usage);
|
|
448
|
+
saveConfigKey(store, (config) => ({ ...config, gitRemoteTimeoutSeconds }));
|
|
449
|
+
return `Git remote total timeout set to ${gitRemoteTimeoutSeconds} seconds\n`;
|
|
450
|
+
},
|
|
451
|
+
clear(store) {
|
|
452
|
+
saveConfigKey(store, (config) => {
|
|
453
|
+
const { gitRemoteTimeoutSeconds: _removed, ...rest } = config;
|
|
454
|
+
return rest;
|
|
455
|
+
});
|
|
456
|
+
return "Git remote total timeout reset to default\n";
|
|
457
|
+
}
|
|
458
|
+
},
|
|
396
459
|
{
|
|
397
460
|
key: "tmux-bin",
|
|
398
461
|
showLabel: "Tmux bin",
|
|
@@ -109,7 +109,7 @@ async function diagnoseProject(args, store, options) {
|
|
|
109
109
|
data: { project, diagnosis: "no-remote" }
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
112
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
113
113
|
const releaseMaintenance = await acquireProjectMaintenanceLock(store.rootDirectory(), project.id, { signal: options.signal });
|
|
114
114
|
try {
|
|
115
115
|
requireUnchangedMaintenanceProject(store, project, "diagnose");
|
|
@@ -153,7 +153,7 @@ async function discoverProjects(args, store, options) {
|
|
|
153
153
|
const workspace = store.getConfig().defaultWorkspace;
|
|
154
154
|
if (workspace === undefined)
|
|
155
155
|
throw usageError("No default workspace is configured; run yui setup.");
|
|
156
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
156
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
157
157
|
const entries = await readdir(workspace, { withFileTypes: true });
|
|
158
158
|
const discovered = [];
|
|
159
159
|
for (const entry of entries) {
|
|
@@ -195,7 +195,7 @@ function renderDiscoveredProjects(projects) {
|
|
|
195
195
|
async function cloneProject(args, store, options) {
|
|
196
196
|
const usage = "Project clone usage: yui project clone <name> <remote> [--alias <name> ...] [--stable <ref>] [--development <ref>] [--external].";
|
|
197
197
|
const parsed = parseCloneArguments(args, usage);
|
|
198
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
198
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
199
199
|
const name = validateProjectName(parsed.name);
|
|
200
200
|
const stableRef = parsed.stable ?? "HEAD";
|
|
201
201
|
const developmentRef = parsed.development ?? stableRef;
|
|
@@ -282,7 +282,7 @@ async function migrateProject(args, store, options) {
|
|
|
282
282
|
if (project.remoteUrl === undefined) {
|
|
283
283
|
throw usageError(`Project migrate requires a remote URL: ${project.id}.`);
|
|
284
284
|
}
|
|
285
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
285
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
286
286
|
const destination = managedProjectPath(store.rootDirectory(), project.id);
|
|
287
287
|
// Migration rewrites the Project's Git repository: hold the per-Project
|
|
288
288
|
// maintenance fence so no rebuild/archive/cleanup (or a second migrate)
|
|
@@ -444,7 +444,7 @@ async function addProject(args, store, options) {
|
|
|
444
444
|
const usage = "Project add usage: yui project add <name> <path> [--alias <name> ...] [--remote <url>] [--stable <ref>] [--development <ref>].";
|
|
445
445
|
const parsed = parseAddArguments(args, usage);
|
|
446
446
|
assertOutsideManagedWorktrees(parsed.path, store.rootDirectory());
|
|
447
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
447
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
448
448
|
const head = await git.inspect(parsed.path, "HEAD");
|
|
449
449
|
assertOutsideManagedWorktrees(head.root, store.rootDirectory());
|
|
450
450
|
if (!await git.isClean(head.root)) {
|
|
@@ -483,7 +483,7 @@ async function updateProject(args, store, options) {
|
|
|
483
483
|
: { developmentBranch: parsed.development })
|
|
484
484
|
};
|
|
485
485
|
assertProjectAvailable(store, updateProjectMetadata(current, patch, now), current.id);
|
|
486
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
486
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
487
487
|
if (parsed.stable !== undefined) {
|
|
488
488
|
const head = await git.inspect(current.path, "HEAD");
|
|
489
489
|
if (!await git.isClean(head.root)) {
|
|
@@ -568,7 +568,7 @@ async function resetProject(args, store, options) {
|
|
|
568
568
|
if (project.stableBranch !== project.developmentBranch) {
|
|
569
569
|
throw usageError(`Project reset requires matching stable and development branches: ${project.id}.`);
|
|
570
570
|
}
|
|
571
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
571
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
572
572
|
const releaseMaintenance = await acquireProjectMaintenanceLock(store.rootDirectory(), project.id, { signal: options.signal });
|
|
573
573
|
try {
|
|
574
574
|
// Re-read under the fence so a concurrent catalog change can never drive
|
|
@@ -693,7 +693,7 @@ async function replaceProject(args, store, options) {
|
|
|
693
693
|
throw usageError(`Project replace discards the current checkout and re-clones from ${project.remoteUrl}. `
|
|
694
694
|
+ "Re-run with --discard-local to acknowledge that the checkout and any uncommitted state will be discarded.");
|
|
695
695
|
}
|
|
696
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
696
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
697
697
|
const releaseMaintenance = await acquireProjectMaintenanceLock(store.rootDirectory(), project.id, { signal: options.signal });
|
|
698
698
|
try {
|
|
699
699
|
const current = requireProject(store, project.id);
|
|
@@ -909,7 +909,7 @@ async function deleteProjectCommand(args, store, options) {
|
|
|
909
909
|
// Heal a crashed earlier attempt before the prechecks turn.
|
|
910
910
|
await healCheckoutSwap({ currentPath: project.path, backupPath: tombstone });
|
|
911
911
|
if (existsSync(project.path)) {
|
|
912
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
912
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
913
913
|
const worktrees = await git.listWorktrees(project.path);
|
|
914
914
|
const linked = worktrees.filter((path) => path !== project.path);
|
|
915
915
|
if (linked.length > 0) {
|
|
@@ -27,7 +27,7 @@ export async function verifyTaskCompletionPublishedTree(taskId, publicationId, s
|
|
|
27
27
|
throw usageError(`Publication ${publication.id} must record exact local and remote commits.`);
|
|
28
28
|
}
|
|
29
29
|
const workspace = requireTaskWorkspace(store, task);
|
|
30
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
30
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
31
31
|
const actualHeads = new Map();
|
|
32
32
|
for (const taskBinding of task.projectBindings) {
|
|
33
33
|
const entry = workspaceProjectEntry(workspace, taskBinding.projectId);
|
|
@@ -123,7 +123,7 @@ async function start(args, store, home, now, options) {
|
|
|
123
123
|
throw usageError(`Task main worktree is not ready; reconcile the Task first: ${task.id}.`);
|
|
124
124
|
}
|
|
125
125
|
taskIntegrationTargetCheckout(store, options.environment, task.id, project.id, targetRef);
|
|
126
|
-
const expectedHead = (await new NodeGitWorkspace().inspect(mainEntry.path, targetRef)).baseCommit;
|
|
126
|
+
const expectedHead = (await new NodeGitWorkspace(() => store.getConfig()).inspect(mainEntry.path, targetRef)).baseCommit;
|
|
127
127
|
const snapshotEntry = candidate.workspace.entries.find(({ projectId }) => projectId === project.id);
|
|
128
128
|
const resultCommit = candidate.gitSnapshot.projects.find(({ projectId }) => projectId === project.id)?.commit;
|
|
129
129
|
if (snapshotEntry === undefined || resultCommit === undefined) {
|
|
@@ -74,7 +74,7 @@ export async function runTaskPublicationAdoptCommand(args, store, options = {})
|
|
|
74
74
|
return { task, publication, completion, acceptedCommit, workspace, entry, integration, actor };
|
|
75
75
|
};
|
|
76
76
|
const facts = store.transaction(read);
|
|
77
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
77
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
78
78
|
const localCommit = facts.publication.localCommit;
|
|
79
79
|
const [acceptedTree, candidateTree, diff] = await Promise.all([
|
|
80
80
|
git.resolveTree(facts.entry.path, facts.acceptedCommit),
|
|
@@ -48,7 +48,7 @@ async function integrateUpstream(args, store, home, options) {
|
|
|
48
48
|
throw usageError(`Task must be active to integrate upstream: ${taskId}/${task.status}.`);
|
|
49
49
|
}
|
|
50
50
|
taskLocalActor(store, options.environment, task.id);
|
|
51
|
-
const git = options.git ?? new NodeGitWorkspace();
|
|
51
|
+
const git = options.git ?? new NodeGitWorkspace(() => store.getConfig());
|
|
52
52
|
const now = options.now ?? (() => new Date());
|
|
53
53
|
await new FileTaskWorkspacePreparer(home, store, git, now)
|
|
54
54
|
.prepareTaskWorkspace(task.id);
|