@lazyingart/agintiflow 0.20.249 → 0.20.251

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.
@@ -379,3 +379,32 @@ local report paths and existing-document revision language now count as local
379
379
  workspace intent, so surrounding research policy does not force a new deep
380
380
  research route. The phrase `page-safe` no longer creates a browser-evidence
381
381
  requirement. Focused routing/evidence smokes and the full npm suite pass.
382
+
383
+ ### Fresh current-turn command evidence on retained sessions
384
+
385
+ `security-labshare-035` exercised a long-lived LocalLLM security-repair
386
+ session after the target repository was already fixed, committed, and clean.
387
+ The follow-up prompt deliberately asked the agent not to edit or recommit. It
388
+ required only the exact hidden verifier and `git status --short`, while allowing
389
+ all other passing implementation and unit-test evidence to remain reusable.
390
+
391
+ Installed AgInTiFlow `0.20.250` incorrectly finished after `inspect_project`.
392
+ It reused the old verifier/status evidence and never executed either command in
393
+ the current turn. The defect was not model quality: the runtime had failed to
394
+ turn explicit commands in a concrete same-task interruption into a fresh,
395
+ revision-bound evidence obligation.
396
+
397
+ The runtime now records explicit current-turn commands in the active execution
398
+ contract and creates a fresh command batch for only that subset. Unrelated
399
+ passing evidence from the unchanged mutation revision remains valid, while old
400
+ runs of the newly requested commands cannot pre-complete the batch. Inline
401
+ command extraction also recognizes natural `verify`, `validate`, `check`,
402
+ `confirm`, and rerun wording in English and Chinese.
403
+
404
+ The same real session was replayed against the patched source at goal revision
405
+ `34`. Its event ledger records both commands under
406
+ `required-command-batch-3`: the exact external security contract returned
407
+ `security_labshare_contract: PASS`, and `git status --short` returned empty.
408
+ The target stayed at commit `2b35928` with no mutation. Focused planning,
409
+ truthful-completion, and dynamic-budget regressions pass, as does the complete
410
+ AgInTiFlow npm suite. The fix is released in AgInTiFlow `0.20.251`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.249",
3
+ "version": "0.20.251",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -99,6 +99,7 @@
99
99
  "scripts/smoke-localllm-auto-max.js",
100
100
  "scripts/smoke-localllm-code-routing.js",
101
101
  "scripts/smoke-local-failure-recovery.js",
102
+ "scripts/smoke-planning-timeout-recovery.js",
102
103
  "scripts/smoke-local-resource-policy.js",
103
104
  "scripts/smoke-localllm-model-tiers.js",
104
105
  "scripts/smoke-localllm-provider.js",
@@ -106,6 +107,7 @@
106
107
  "scripts/smoke-truthful-completion.js",
107
108
  "scripts/smoke-document-artifact-quality.js",
108
109
  "scripts/smoke-writing-specialist-routing.js",
110
+ "scripts/smoke-workspace-secret-false-positive.js",
109
111
  "scripts/eval-provider-attribution.js",
110
112
  "scripts/local-first-agent-eval.mjs",
111
113
  "scripts/fixtures/local-first-agent-eval-fixtures.mjs",
@@ -171,6 +173,7 @@
171
173
  "smoke:localllm-auto-max": "node scripts/smoke-localllm-auto-max.js",
172
174
  "smoke:localllm-code-routing": "node scripts/smoke-localllm-code-routing.js",
173
175
  "smoke:local-failure-recovery": "node scripts/smoke-local-failure-recovery.js",
176
+ "smoke:planning-timeout-recovery": "node scripts/smoke-planning-timeout-recovery.js",
174
177
  "smoke:local-resource-policy": "node scripts/smoke-local-resource-policy.js",
175
178
  "smoke:context-budget-recovery": "node scripts/smoke-context-budget-recovery.js",
176
179
  "smoke:localllm-model-tiers": "node scripts/smoke-localllm-model-tiers.js",
@@ -179,6 +182,7 @@
179
182
  "smoke:truthful-completion": "node scripts/smoke-truthful-completion.js",
180
183
  "smoke:document-artifact-quality": "node scripts/smoke-document-artifact-quality.js",
181
184
  "smoke:writing-specialist-routing": "node scripts/smoke-writing-specialist-routing.js",
185
+ "smoke:workspace-secret-false-positive": "node scripts/smoke-workspace-secret-false-positive.js",
182
186
  "smoke:mcp": "node scripts/smoke-mcp.js",
183
187
  "smoke:model-roles": "node scripts/smoke-model-roles.js",
184
188
  "smoke:platform": "node scripts/smoke-platform.js",
@@ -223,7 +227,8 @@
223
227
  "storage:migrate": "node bin/aginti-cli.js storage migrate",
224
228
  "publish:env": "node scripts/npm-publish-from-env.js publish --access public",
225
229
  "publish:env:whoami": "node scripts/npm-publish-from-env.js whoami",
226
- "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
230
+ "pretest": "npm run smoke:planning-timeout-recovery",
231
+ "test": "npm run check && npm run smoke:integration-production-mount && npm run smoke:integration-production-runtime-bundle && npm run smoke:integration-retained-idempotency-store && npm run smoke:integration-storage-authority && npm run smoke:integration-retained-durable-common && npm run smoke:integration-retained-file-lock && npm run smoke:integration-retained-event-ledger && npm run smoke:integration-retained-event-ledger-bundle && npm run smoke:integration-retained-repository-kernel && npm run smoke:integration-retained-session-state-store && npm run smoke:integration-retained-native-session-repository-state && npm run smoke:integration-retained-native-execution-evidence && npm run smoke:integration-retained-runtime-repository-surface && npm run smoke:integration-retained-runtime-repository-phase-b && npm run smoke:integration-runtime-repository-contract && npm run smoke:integration-runtime-authority && npm run smoke:localllm-provider && npm run smoke:localllm-model-tiers && npm run smoke:localllm-code-routing && npm run smoke:local-failure-recovery && npm run smoke:localllm-auto-max && npm run smoke:local-resource-policy && npm run smoke:context-budget-recovery && npm run smoke:session-runtime && npm run smoke:runtime-core && npm run smoke:progressive-tools && npm run smoke:truthful-completion && npm run smoke:document-artifact-quality && npm run smoke:writing-specialist-routing && npm run smoke:workspace-secret-false-positive && npm run eval:local-first-agent && npm run eval:provider-attribution && npm run smoke:runtime-compat && npm run smoke:autoupdate && npm run smoke:web-api && npm run smoke:math-rendering && npm run smoke:web-ui && npm run smoke:web-autostart && npm run smoke:webapp-command && npm run smoke:web-port-fallback && npm run smoke:docker-command && npm run smoke:coding-tools && npm run smoke:dynamic-step-budget && npm run smoke:execution-policy && npm run smoke:aaps-adapter && npm run smoke:auxiliary-tools && npm run smoke:perception-research && npm run smoke:deep-research && npm run smoke:public-research && npm run smoke:safe-chat && npm run smoke:auth && npm run smoke:agentlink && npm run smoke:canvas-artifacts && npm run smoke:capabilities && npm run smoke:mcp && npm run smoke:model-roles && npm run smoke:platform && npm run smoke:permission-modes && npm run smoke:skills && npm run smoke:skillmesh && npm run smoke:supervision-ledger && npm run smoke:tmux-tools && npm run smoke:long-jobs && npm run smoke:run-stdin && npm run smoke:cli-chat && npm run smoke:inbox",
227
232
  "pack:dry-run": "npm pack --dry-run",
228
233
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
229
234
  },
@@ -971,6 +971,55 @@ try {
971
971
  const pythonDemoPolicy = evaluateCommandPolicy("python3 demo.py 2>&1", dockerWorkspaceNoInstallsPolicy);
972
972
  assert(pythonDemoPolicy.allowed, "workspace-local python demo script should be allowed without package installs");
973
973
  assert(pythonDemoPolicy.category === "toolchain", "workspace-local python demo script should be classified as toolchain");
974
+ const broadLocalProbePolicy = evaluateCommandPolicy(
975
+ 'python3 --version; echo "---IMPORT TEST---"; python3 -c "import pathlib; print(pathlib.Path.cwd())" 2>&1; echo "---FILES---"; ls -la 2>&1 | head -20',
976
+ dockerWorkspaceNoInstallsPolicy
977
+ );
978
+ assert(
979
+ broadLocalProbePolicy.allowed && broadLocalProbePolicy.category === "general-shell",
980
+ "networkless general shell inside docker-workspace should not require package-install permission"
981
+ );
982
+ const broadLocalHostProbePolicy = evaluateCommandPolicy(
983
+ 'python3 -c "import pathlib; print(pathlib.Path.cwd())"',
984
+ { ...hostWorkspacePolicy, packageInstallPolicy: "block" }
985
+ );
986
+ assert(
987
+ !broadLocalHostProbePolicy.allowed,
988
+ "general shell on the host should remain blocked without trusted destructive access"
989
+ );
990
+ const explicitBlockedInstallPolicy = evaluateCommandPolicy(
991
+ "python3 -m pip install requests",
992
+ dockerWorkspaceNoInstallsPolicy
993
+ );
994
+ assert(
995
+ !explicitBlockedInstallPolicy.allowed && explicitBlockedInstallPolicy.category === "package-install",
996
+ "decoupling broad shell from install authorization must not allow explicit package installs"
997
+ );
998
+ const destructiveNoInstallsPolicy = evaluateCommandPolicy("rm -rf reports", dockerWorkspaceNoInstallsPolicy);
999
+ assert(
1000
+ !destructiveNoInstallsPolicy.allowed && destructiveNoInstallsPolicy.category === "destructive",
1001
+ "decoupling broad shell from install authorization must not weaken destructive-command guards"
1002
+ );
1003
+ const multiParagraphCommitPolicy = evaluateCommandPolicy(
1004
+ 'git commit -m "Harden LabShare" -m "Fix path traversal, avoid shell injection, and preserve normal use."',
1005
+ dockerWorkspaceNoInstallsPolicy
1006
+ );
1007
+ assert(
1008
+ multiParagraphCommitPolicy.allowed &&
1009
+ multiParagraphCommitPolicy.category === "git-workflow" &&
1010
+ multiParagraphCommitPolicy.gitOnly === true,
1011
+ "a bounded multi-paragraph Git commit message should remain a Git-only workflow"
1012
+ );
1013
+ const boundedTaskCommitPolicy = evaluateCommandPolicy(
1014
+ "git add -- 'labshare.py' 'tests/test_labshare.py' 'SECURITY.md' && git commit -m 'Harden LabShare'",
1015
+ dockerWorkspaceNoInstallsPolicy
1016
+ );
1017
+ assert(
1018
+ boundedTaskCommitPolicy.allowed &&
1019
+ boundedTaskCommitPolicy.category === "git-workflow" &&
1020
+ boundedTaskCommitPolicy.gitOnly === true,
1021
+ "the task-owned add-and-commit routine should remain a bounded Git workflow"
1022
+ );
974
1023
  const rVersionProbePolicy = evaluateCommandPolicy("R --version 2>&1", dockerWorkspaceNoInstallsPolicy);
975
1024
  assert(rVersionProbePolicy.allowed, "R version probe should be allowed without package installs");
976
1025
  assert(rVersionProbePolicy.category === "read-only", "R version probe should be classified as read-only");
@@ -1218,6 +1267,38 @@ try {
1218
1267
  });
1219
1268
  assert(permissionAdvice.suggestedCommand.includes("coding-policy-smoke"), "permission advice did not include resume session id");
1220
1269
  assert(permissionAdvice.suggestedCommand.includes("--sandbox-mode docker-workspace"), "permission advice did not suggest docker-workspace recovery");
1270
+ const wrongWorkspaceCdAdvice = buildPermissionAdvice({
1271
+ toolName: "run_command",
1272
+ args: {
1273
+ command:
1274
+ "cd /home/example/project-parent && python3 -m unittest discover -s tests -v",
1275
+ },
1276
+ guard: {
1277
+ category: "blocked",
1278
+ reason:
1279
+ "cd target must be a safe workspace-relative directory: /home/example/project-parent",
1280
+ },
1281
+ config: {
1282
+ ...dockerWorkspacePolicy,
1283
+ commandCwd: "/home/example/project-parent/current-project",
1284
+ },
1285
+ state: { sessionId: "coding-wrong-workspace-cd-smoke" },
1286
+ });
1287
+ assert(
1288
+ wrongWorkspaceCdAdvice.autoRecover === true,
1289
+ "an unnecessary outside-workspace cd should be corrected in-turn instead of pausing"
1290
+ );
1291
+ assert(
1292
+ /configured project root/i.test(wrongWorkspaceCdAdvice.instruction),
1293
+ "workspace cd correction did not direct the model back to the configured project root"
1294
+ );
1295
+ assert(
1296
+ !shouldPauseForPermissionAdvice({
1297
+ blocked: true,
1298
+ permissionAdvice: wrongWorkspaceCdAdvice,
1299
+ }),
1300
+ "recoverable workspace cd correction still paused the session"
1301
+ );
1221
1302
  const destructiveAdvice = buildPermissionAdvice({
1222
1303
  toolName: "run_command",
1223
1304
  args: { command: "rm -rf reports && git reset --hard" },
@@ -1367,6 +1448,32 @@ try {
1367
1448
  !shouldPauseForPermissionAdvice({ blocked: true, permissionAdvice: dynamicEvidenceAdvice }),
1368
1449
  "dynamic evidence filename recovery still produced a permission pause"
1369
1450
  );
1451
+ const malformedPatchGuard = {
1452
+ allowed: false,
1453
+ category: "workspace-patch",
1454
+ reason: "Patch did not contain any supported file operations.",
1455
+ permissionAdvice: {
1456
+ category: "workspace-patch",
1457
+ autoRecover: true,
1458
+ summary: "The patch format or exact context was not accepted; this is not a permission blocker.",
1459
+ instruction: "Inspect the exact file and retry with a supported focused edit.",
1460
+ },
1461
+ };
1462
+ const malformedPatchAdvice = buildPermissionAdvice({
1463
+ toolName: "apply_patch",
1464
+ args: { patch: "*** a/labshare.py\n*** b/labshare.py" },
1465
+ guard: malformedPatchGuard,
1466
+ config: dockerWorkspacePolicy,
1467
+ state: { sessionId: "coding-malformed-patch-smoke" },
1468
+ });
1469
+ assert(
1470
+ malformedPatchAdvice === malformedPatchGuard.permissionAdvice,
1471
+ "explicit recoverable workspace-patch advice was replaced by generic permission advice"
1472
+ );
1473
+ assert(
1474
+ !shouldPauseForPermissionAdvice({ blocked: true, permissionAdvice: malformedPatchAdvice }),
1475
+ "recoverable malformed patch advice still paused the session for permission"
1476
+ );
1370
1477
  const destructiveDynamicEvidenceAdvice = buildPermissionAdvice({
1371
1478
  toolName: "run_command",
1372
1479
  args: {
@@ -1408,6 +1515,104 @@ try {
1408
1515
  /once for each rendered page/i.test(documentPageBatchAdvice.instruction),
1409
1516
  "Word document page batching did not recover into separate visual checks"
1410
1517
  );
1518
+ const alreadyCommittedAdvice = buildFailedCommandAdvice({
1519
+ args: { command: "git commit -m 'Focused repair'" },
1520
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1521
+ commandResult: {
1522
+ ok: false,
1523
+ exitCode: 1,
1524
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1525
+ },
1526
+ config: dockerWorkspacePolicy,
1527
+ state: {
1528
+ meta: {
1529
+ goalContract: { revision: 9 },
1530
+ projectVerification: { mutationRevision: 4 },
1531
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1532
+ },
1533
+ },
1534
+ });
1535
+ assert(
1536
+ alreadyCommittedAdvice?.failureKind === "repository-already-committed" &&
1537
+ alreadyCommittedAdvice.autoRecover === true &&
1538
+ /should not be retried/i.test(alreadyCommittedAdvice.summary),
1539
+ "a clean no-op commit with current durable evidence did not converge"
1540
+ );
1541
+ const sameTaskContinuationAdvice = buildFailedCommandAdvice({
1542
+ args: { command: "git commit -m 'Focused repair'" },
1543
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1544
+ commandResult: {
1545
+ ok: false,
1546
+ exitCode: 1,
1547
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1548
+ },
1549
+ config: dockerWorkspacePolicy,
1550
+ state: {
1551
+ meta: {
1552
+ goalContract: {
1553
+ revision: 10,
1554
+ history: [
1555
+ { revision: 9, taskHash: "task-a" },
1556
+ { revision: 10, taskHash: "task-a" },
1557
+ ],
1558
+ },
1559
+ projectVerification: { mutationRevision: 4 },
1560
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1561
+ },
1562
+ },
1563
+ });
1564
+ assert(
1565
+ sameTaskContinuationAdvice?.failureKind === "repository-already-committed",
1566
+ "a same-task continuation invalidated a clean commit at the current mutation revision"
1567
+ );
1568
+ const newTaskAdvice = buildFailedCommandAdvice({
1569
+ args: { command: "git commit -m 'Focused repair'" },
1570
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1571
+ commandResult: {
1572
+ ok: false,
1573
+ exitCode: 1,
1574
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1575
+ },
1576
+ config: dockerWorkspacePolicy,
1577
+ state: {
1578
+ meta: {
1579
+ goalContract: {
1580
+ revision: 10,
1581
+ history: [
1582
+ { revision: 9, taskHash: "task-a" },
1583
+ { revision: 10, taskHash: "task-b" },
1584
+ ],
1585
+ },
1586
+ projectVerification: { mutationRevision: 4 },
1587
+ durableGitEvidence: [{ action: "commit", goalRevision: 9, mutationRevision: 4 }],
1588
+ },
1589
+ },
1590
+ });
1591
+ assert(
1592
+ newTaskAdvice === null,
1593
+ "a commit from a prior task satisfied a genuinely new task"
1594
+ );
1595
+ const staleCommitAdvice = buildFailedCommandAdvice({
1596
+ args: { command: "git commit -m 'Focused repair'" },
1597
+ commandPolicy: evaluateCommandPolicy("git commit -m 'Focused repair'", dockerWorkspacePolicy),
1598
+ commandResult: {
1599
+ ok: false,
1600
+ exitCode: 1,
1601
+ stdout: "On branch main\nnothing to commit, working tree clean\n",
1602
+ },
1603
+ config: dockerWorkspacePolicy,
1604
+ state: {
1605
+ meta: {
1606
+ goalContract: { revision: 9 },
1607
+ projectVerification: { mutationRevision: 4 },
1608
+ durableGitEvidence: [{ action: "commit", goalRevision: 8, mutationRevision: 4 }],
1609
+ },
1610
+ },
1611
+ });
1612
+ assert(
1613
+ staleCommitAdvice === null,
1614
+ "a stale commit from an earlier goal revision satisfied a fresh commit request"
1615
+ );
1411
1616
  const failedNetworkAdvice = buildFailedCommandAdvice({
1412
1617
  args: { command: "git clone https://github.com/lazyingart/AgInTiFlow.git" },
1413
1618
  commandPolicy: clonePolicy,
@@ -6,12 +6,52 @@ import os from "node:os";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { promisify } from "node:util";
9
+ import {
10
+ dockerWorkspaceAliasMounts,
11
+ ensureDockerGitIdentity,
12
+ } from "../src/docker-sandbox.js";
9
13
 
10
14
  const execFileAsync = promisify(execFile);
11
15
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
12
16
  const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-docker-command-"));
13
17
 
14
18
  try {
19
+ const identityRepo = path.join(tempRoot, "identity-repo");
20
+ const dockerState = path.join(tempRoot, "docker-state");
21
+ await fs.mkdir(identityRepo, { recursive: true });
22
+ await fs.mkdir(path.join(dockerState, "home"), { recursive: true });
23
+ await execFileAsync("git", ["init", identityRepo]);
24
+ await execFileAsync("git", ["-C", identityRepo, "config", "user.name", "AgInTi Test Author"]);
25
+ await execFileAsync("git", ["-C", identityRepo, "config", "user.email", "aginti-test@example.invalid"]);
26
+ assert.deepEqual(
27
+ dockerWorkspaceAliasMounts({ commandCwd: identityRepo, sandboxMode: "docker-workspace" }),
28
+ [identityRepo],
29
+ "Docker workspace mode should preserve the exact host workspace path as a scoped writable alias"
30
+ );
31
+ assert.deepEqual(
32
+ dockerWorkspaceAliasMounts({ commandCwd: identityRepo, sandboxMode: "docker-readonly" }),
33
+ [],
34
+ "Read-only Docker mode must not create a writable host-path alias"
35
+ );
36
+ const identity = await ensureDockerGitIdentity(
37
+ { commandCwd: identityRepo, dockerStateDir: dockerState },
38
+ {
39
+ root: dockerState,
40
+ home: path.join(dockerState, "home"),
41
+ cache: path.join(dockerState, "cache"),
42
+ env: path.join(dockerState, "env"),
43
+ }
44
+ );
45
+ assert.equal(identity.configured, true, "Docker Git identity should reuse an existing repository author");
46
+ const identityConfig = path.join(dockerState, "home", ".gitconfig.aginti-author");
47
+ const [storedName, storedEmail] = await Promise.all([
48
+ execFileAsync("git", ["config", "--file", identityConfig, "--get", "user.name"]),
49
+ execFileAsync("git", ["config", "--file", identityConfig, "--get", "user.email"]),
50
+ ]);
51
+ assert.equal(storedName.stdout.trim(), "AgInTi Test Author");
52
+ assert.equal(storedEmail.stdout.trim(), "aginti-test@example.invalid");
53
+ assert.equal((await fs.stat(identityConfig)).mode & 0o777, 0o600, "Docker author config should be private");
54
+
15
55
  const { stdout } = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "docker", "status", "--json", "--cwd", tempRoot], {
16
56
  cwd: tempRoot,
17
57
  env: {