@aiwg/cli 2026.10.3 → 2026.10.4
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 +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +98 -37
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +51 -11
- package/dist/src/sessions/contracts.js +40 -2
- package/dist/src/sessions/coverage-preflight.js +1 -1
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +147 -52
- package/dist/src/sessions/workspace-discovery.js +221 -56
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { access, mkdir, readFile, realpath, rename, stat, writeFile, } from 'node:fs/promises';
|
|
1
|
+
import { access, mkdir, readFile, readdir, realpath, rename, stat, writeFile, } from 'node:fs/promises';
|
|
3
2
|
import { homedir, userInfo } from 'node:os';
|
|
4
3
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
5
|
-
import { createInterface } from 'node:readline';
|
|
6
4
|
import { inspectMuseNativeWorkspace } from './adapters/muse.js';
|
|
7
5
|
import { readOmpSessionHeader } from './adapters/omp.js';
|
|
8
6
|
import { grokbotConversationIdFromLocator } from './adapters/grokbot-locator.js';
|
|
@@ -11,7 +9,9 @@ import { SESSION_PROVIDER_IDS, SessionCoveragePreflightSchema, SessionContractEr
|
|
|
11
9
|
import { redactSourceLocator } from './discovery.js';
|
|
12
10
|
import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
13
11
|
import { coveragePreflight, expectedSessionProviders } from './coverage-preflight.js';
|
|
14
|
-
import { fingerprintSourceFile } from './readers.js';
|
|
12
|
+
import { fingerprintSourceFile, streamJsonLines } from './readers.js';
|
|
13
|
+
import { matchSessionWorkspace } from './workspace-matching.js';
|
|
14
|
+
import { matchImportExclusion, nativeSessionIdFromLocator, resolveImportExclusions } from './import-exclusions.js';
|
|
15
15
|
export const DISCOVERY_MANIFEST_VERSION = '1.0.0';
|
|
16
16
|
const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
17
17
|
'copilot', 'hermes', 'opencode', 'openclaw', 'openhuman', 'grok-build',
|
|
@@ -19,13 +19,19 @@ const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
|
19
19
|
]);
|
|
20
20
|
export async function discoverWorkspaceHistories(options) {
|
|
21
21
|
const registry = options.registry ?? createSessionAdapterRegistry();
|
|
22
|
+
const exclusions = resolveImportExclusions(undefined, options.exclude);
|
|
22
23
|
const expectedProviders = expectedSessionProviders(options.expectedProviders ?? SESSION_PROVIDER_IDS);
|
|
23
24
|
const supportedProviders = new Set(registry.list().map((adapter) => adapter.provider));
|
|
24
25
|
const workspacePath = await canonicalPath(options.workspace);
|
|
25
26
|
const workspaceId = workspacePath;
|
|
27
|
+
const workspacePaths = [...new Set([workspacePath, resolve(options.workspace),
|
|
28
|
+
...(options.workspaceAliases ?? []).map((alias) => resolve(alias))])];
|
|
29
|
+
for (const alias of workspacePaths) {
|
|
30
|
+
if (await canonicalPath(alias) !== workspacePath) {
|
|
31
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'workspace alias resolves to a different workspace');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
26
34
|
const providerHomes = providerHomeCandidates(options.providerHome, options.operatorHome);
|
|
27
|
-
const keyWithLeadingDash = workspaceKey(workspacePath, true);
|
|
28
|
-
const keyWithoutLeadingDash = workspaceKey(workspacePath, false);
|
|
29
35
|
const discoverable = [
|
|
30
36
|
{ provider: 'deepseek-harness', roots: options.dshRoot
|
|
31
37
|
? [resolve(options.dshRoot)] : options.providerHome
|
|
@@ -46,26 +52,23 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
46
52
|
? [resolve(options.grokbotRoot)] : [] },
|
|
47
53
|
{
|
|
48
54
|
provider: 'claude',
|
|
49
|
-
roots: providerHomes.
|
|
55
|
+
roots: providerHomes.flatMap((providerHome) => workspacePaths.map((workspace) => join(providerHome, '.claude', 'projects', claudeProjectKey(workspace)))),
|
|
50
56
|
},
|
|
51
57
|
{
|
|
52
58
|
provider: 'codex',
|
|
53
|
-
roots: options.
|
|
54
|
-
? [
|
|
55
|
-
: options.providerHome
|
|
56
|
-
? [join(resolve(options.providerHome), '.codex', 'sessions')]
|
|
57
|
-
: [],
|
|
59
|
+
roots: options.authorizedRoots || !expectedProviders.includes('codex')
|
|
60
|
+
? [] : await codexDiscoveryRoots(options),
|
|
58
61
|
},
|
|
59
62
|
{
|
|
60
63
|
provider: 'cursor',
|
|
61
|
-
roots: providerHomes.
|
|
64
|
+
roots: providerHomes.flatMap((providerHome) => workspacePaths.map((workspace) => join(providerHome, '.cursor', 'projects', workspaceKey(workspace, false), 'agent-transcripts'))),
|
|
62
65
|
},
|
|
63
66
|
{
|
|
64
67
|
provider: 'factory',
|
|
65
|
-
roots: providerHomes.flatMap((providerHome) => [
|
|
66
|
-
join(providerHome, '.factory', 'projects',
|
|
67
|
-
join(providerHome, '.factory', 'sessions',
|
|
68
|
-
]),
|
|
68
|
+
roots: providerHomes.flatMap((providerHome) => workspacePaths.flatMap((workspace) => [
|
|
69
|
+
join(providerHome, '.factory', 'projects', workspaceKey(workspace, true)),
|
|
70
|
+
join(providerHome, '.factory', 'sessions', workspaceKey(workspace, true)),
|
|
71
|
+
])),
|
|
69
72
|
},
|
|
70
73
|
{
|
|
71
74
|
// Pi stores every working directory's sessions in its own
|
|
@@ -79,9 +82,10 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
79
82
|
provider: 'pi',
|
|
80
83
|
roots: (process.env.PI_CODING_AGENT_SESSION_DIR
|
|
81
84
|
? [resolve(process.env.PI_CODING_AGENT_SESSION_DIR)]
|
|
82
|
-
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).
|
|
85
|
+
: providerHomes.map((providerHome) => join(providerHome, '.pi', 'agent', 'sessions'))).flatMap((sessionsRoot) => workspacePaths.map((workspace) => join(sessionsRoot, `--${workspaceKey(workspace, false)}--`))),
|
|
83
86
|
},
|
|
84
87
|
];
|
|
88
|
+
const authorizedRoots = { ...options.authorizedRoots };
|
|
85
89
|
const reports = new Map();
|
|
86
90
|
const candidates = [];
|
|
87
91
|
const seen = new Set();
|
|
@@ -92,9 +96,27 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
92
96
|
if (adapter.disposition === 'unsupported' || !adapter.supportedOperations.includes('discover'))
|
|
93
97
|
continue;
|
|
94
98
|
const availableRoots = [];
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
const rootCounts = new Map();
|
|
100
|
+
for (const root of options.authorizedRoots ? options.authorizedRoots[entry.provider] ?? [] : entry.roots) {
|
|
101
|
+
if (await pathExists(root)) {
|
|
102
|
+
const canonical = await canonicalPath(root);
|
|
103
|
+
if (options.authorizedRoots && canonical !== root) {
|
|
104
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'previously authorized provider root changed; rediscover with explicit authorization');
|
|
105
|
+
}
|
|
106
|
+
if (!availableRoots.includes(canonical)) {
|
|
107
|
+
availableRoots.push(canonical);
|
|
108
|
+
rootCounts.set(canonical, {
|
|
109
|
+
root: redactSourceLocator(canonical), rootId: sha256(canonical),
|
|
110
|
+
status: 'checked', sourceCount: 0, duplicateCount: 0,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
rootCounts.set(root, {
|
|
116
|
+
root: redactSourceLocator(root), rootId: sha256(root),
|
|
117
|
+
status: 'unavailable', sourceCount: 0, duplicateCount: 0,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
98
120
|
}
|
|
99
121
|
if (availableRoots.length === 0) {
|
|
100
122
|
const codexNeedsAuthorization = (entry.provider === 'codex' || entry.provider === 'omp'
|
|
@@ -106,15 +128,44 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
106
128
|
: 'PROVIDER_ROOT_UNAVAILABLE', codexNeedsAuthorization
|
|
107
129
|
? `Pass --${entry.provider === 'deepseek-harness' ? 'dsh' : entry.provider}-root with an explicitly authorized sessions root.`
|
|
108
130
|
: `No authorized ${entry.provider} workspace history root was found.`));
|
|
131
|
+
if (entry.provider === 'codex')
|
|
132
|
+
reports.get(entry.provider).rootCounts = [...rootCounts.values()];
|
|
109
133
|
continue;
|
|
110
134
|
}
|
|
135
|
+
authorizedRoots[entry.provider] = availableRoots;
|
|
111
136
|
const scope = {
|
|
112
137
|
workspaceId,
|
|
113
138
|
allowedRoots: availableRoots,
|
|
114
139
|
};
|
|
115
140
|
const providerSources = [];
|
|
141
|
+
let matchedBySubfolder = 0;
|
|
142
|
+
let matchedByWorktree = 0;
|
|
116
143
|
const museSourceInspections = [];
|
|
117
144
|
for await (const descriptor of adapter.discover(scope)) {
|
|
145
|
+
const known = options.knownSources?.find(source => source.provider === descriptor.provider && source.locator === descriptor.locator);
|
|
146
|
+
const nativeSessionId = known?.nativeSessionId ?? descriptor.nativeSessionId ?? nativeSessionIdFromLocator(descriptor);
|
|
147
|
+
const exclusion = matchImportExclusion({ ...descriptor, nativeSessionId }, exclusions);
|
|
148
|
+
if (exclusion) {
|
|
149
|
+
const locator = resolve(descriptor.locator);
|
|
150
|
+
const authorizedRoot = scope.allowedRoots.find(root => locator === root || locator.startsWith(`${root}/`));
|
|
151
|
+
if (!authorizedRoot)
|
|
152
|
+
continue;
|
|
153
|
+
const key = `excluded\0${entry.provider}\0${locator}`;
|
|
154
|
+
if (seen.has(key))
|
|
155
|
+
continue;
|
|
156
|
+
seen.add(key);
|
|
157
|
+
const source = {
|
|
158
|
+
sourceId: known?.sourceId ?? sha256(key), provider: entry.provider,
|
|
159
|
+
locator, redactedLocator: redactSourceLocator(locator), locatorClass: descriptor.locatorClass,
|
|
160
|
+
authorizedRoot, modifiedAt: '', sizeBytes: 0, digest: '', nativeSessionId,
|
|
161
|
+
status: 'excluded', exclusion,
|
|
162
|
+
};
|
|
163
|
+
rootCounts.get(authorizedRoot).sourceCount += 1;
|
|
164
|
+
providerSources.push(source);
|
|
165
|
+
candidates.push(source);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
let workspaceMatch = null;
|
|
118
169
|
if (entry.provider === 'muse') {
|
|
119
170
|
const inspection = await inspectMuseNativeWorkspace({
|
|
120
171
|
...descriptor,
|
|
@@ -124,31 +175,54 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
124
175
|
museSourceInspections.push({
|
|
125
176
|
sourceKey: sha256(['muse-workspace-inspection-v1', descriptor.locator].join('\0')),
|
|
126
177
|
redactedLocator: redactSourceLocator(descriptor.locator),
|
|
178
|
+
nativeSessionId: basename(dirname(descriptor.locator)),
|
|
127
179
|
...inspection,
|
|
128
180
|
});
|
|
129
181
|
if (inspection.outcome !== 'matched')
|
|
130
182
|
continue;
|
|
183
|
+
workspaceMatch = inspection.matchKind ?? null;
|
|
131
184
|
}
|
|
132
185
|
const locator = await canonicalPath(descriptor.locator);
|
|
133
|
-
if (entry.provider === 'codex'
|
|
134
|
-
|
|
135
|
-
|
|
186
|
+
if (entry.provider === 'codex') {
|
|
187
|
+
workspaceMatch = await codexSourceWorkspaceMatch(locator, workspacePath);
|
|
188
|
+
if (!workspaceMatch)
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
136
191
|
const ompHeader = entry.provider === 'omp' ? await readOmpSessionHeader({ ...descriptor, sourceId: 'discovery', authorizedScope: scope }) : undefined;
|
|
137
|
-
if (ompHeader
|
|
138
|
-
|
|
192
|
+
if (ompHeader) {
|
|
193
|
+
workspaceMatch = await matchSessionWorkspace(ompHeader.cwd, workspacePath);
|
|
194
|
+
if (!workspaceMatch)
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
139
197
|
const details = await stat(locator);
|
|
140
198
|
const authorizedRoot = scope.allowedRoots.find((root) => locator === root || locator.startsWith(`${root}/`));
|
|
141
199
|
if (!authorizedRoot)
|
|
142
200
|
continue;
|
|
201
|
+
if (entry.provider === 'claude'
|
|
202
|
+
&& !await claudeSourceMatchesWorkspace(locator, workspacePath))
|
|
203
|
+
continue;
|
|
143
204
|
const fingerprint = await fingerprintSourceFile({
|
|
144
205
|
selectedPath: locator,
|
|
145
206
|
allowedRoots: [authorizedRoot],
|
|
146
207
|
});
|
|
147
|
-
const
|
|
148
|
-
|
|
208
|
+
const rolloutId = entry.provider === 'codex' && descriptor.locatorClass === 'codex-rollout-jsonl'
|
|
209
|
+
? await codexRolloutId(locator) : undefined;
|
|
210
|
+
const dedupeKey = rolloutId
|
|
211
|
+
? `codex\0rollout\0${rolloutId}`
|
|
212
|
+
: `${entry.provider}\0${fingerprint.digest}\0${fingerprint.size}`;
|
|
213
|
+
const rootCount = rootCounts.get(authorizedRoot);
|
|
214
|
+
if (seen.has(dedupeKey)) {
|
|
215
|
+
rootCount.duplicateCount += 1;
|
|
149
216
|
continue;
|
|
217
|
+
}
|
|
150
218
|
seen.add(dedupeKey);
|
|
219
|
+
if (workspaceMatch === 'subfolder')
|
|
220
|
+
matchedBySubfolder += 1;
|
|
221
|
+
if (workspaceMatch === 'worktree')
|
|
222
|
+
matchedByWorktree += 1;
|
|
223
|
+
rootCount.sourceCount += 1;
|
|
151
224
|
const source = sourceFromDescriptor(descriptor, locator, authorizedRoot, details, fingerprint.digest);
|
|
225
|
+
source.nativeSessionId = rolloutId ?? ompHeader?.id ?? nativeSessionId;
|
|
152
226
|
if (ompHeader)
|
|
153
227
|
source.sourceId = sha256(['omp-native-source-v1', workspaceId, ompHeader.id].join('\0'));
|
|
154
228
|
if (entry.provider === 'grokbot') {
|
|
@@ -168,13 +242,17 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
168
242
|
const incompleteMuseInspection = museSourceInspections.some((inspection) => inspection.outcome === 'inconclusive');
|
|
169
243
|
const report = providerReport(entry.provider, incompleteMuseInspection ? 'not-checked' : 'checked', 'discoverable', providerSources, incompleteMuseInspection ? 'MUSE_SOURCE_INSPECTION_INCOMPLETE'
|
|
170
244
|
: providerSources.length === 0 ? 'NO_WORKSPACE_SOURCES' : null, incompleteMuseInspection
|
|
171
|
-
? 'Review museSourceInspections; authorize a supported Muse export for unresolved sources before claiming workspace coverage.'
|
|
245
|
+
? 'Review unattributed museSourceInspections and their limit name/value; authorize a supported Muse export for unresolved sources before claiming workspace coverage.'
|
|
172
246
|
: providerSources.length === 0
|
|
173
247
|
? `The ${entry.provider} root was checked, but no source matched the authorized workspace.`
|
|
174
248
|
: null);
|
|
175
249
|
if (entry.provider === 'muse') {
|
|
176
250
|
report.museSourceInspections = museSourceInspections.sort((a, b) => a.sourceKey.localeCompare(b.sourceKey));
|
|
177
251
|
}
|
|
252
|
+
report.matchedBySubfolder = matchedBySubfolder;
|
|
253
|
+
report.matchedByWorktree = matchedByWorktree;
|
|
254
|
+
if (entry.provider === 'codex')
|
|
255
|
+
report.rootCounts = [...rootCounts.values()];
|
|
178
256
|
reports.set(entry.provider, report);
|
|
179
257
|
}
|
|
180
258
|
for (const provider of expectedProviders) {
|
|
@@ -203,6 +281,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
203
281
|
schemaVersion: DISCOVERY_MANIFEST_VERSION,
|
|
204
282
|
workspaceId,
|
|
205
283
|
preflight,
|
|
284
|
+
authorizedRoots,
|
|
206
285
|
sources: sources.map((source) => ({
|
|
207
286
|
sourceId: source.sourceId,
|
|
208
287
|
provider: source.provider,
|
|
@@ -211,11 +290,16 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
211
290
|
modifiedAt: source.modifiedAt,
|
|
212
291
|
sizeBytes: source.sizeBytes,
|
|
213
292
|
digest: source.digest,
|
|
293
|
+
...(source.nativeSessionId ? { nativeSessionId: source.nativeSessionId } : {}),
|
|
294
|
+
...(source.status ? { status: source.status, exclusion: source.exclusion } : {}),
|
|
214
295
|
})),
|
|
215
296
|
providers: providers.map((provider) => ({
|
|
216
297
|
provider: provider.provider,
|
|
217
298
|
status: provider.status,
|
|
218
299
|
reasonCode: provider.reasonCode,
|
|
300
|
+
...(provider.matchedBySubfolder !== undefined ? { matchedBySubfolder: provider.matchedBySubfolder } : {}),
|
|
301
|
+
...(provider.matchedByWorktree !== undefined ? { matchedByWorktree: provider.matchedByWorktree } : {}),
|
|
302
|
+
...(provider.rootCounts ? { rootCounts: provider.rootCounts } : {}),
|
|
219
303
|
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
220
304
|
})),
|
|
221
305
|
};
|
|
@@ -226,6 +310,7 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
226
310
|
workspaceId,
|
|
227
311
|
workspacePath,
|
|
228
312
|
preflight,
|
|
313
|
+
authorizedRoots,
|
|
229
314
|
providers,
|
|
230
315
|
sources,
|
|
231
316
|
};
|
|
@@ -272,6 +357,9 @@ export function publicDiscoveryManifest(manifest) {
|
|
|
272
357
|
totals: {
|
|
273
358
|
providers: manifest.providers.length,
|
|
274
359
|
sources: manifest.sources.length,
|
|
360
|
+
excluded: manifest.sources.filter(source => source.status === 'excluded').length,
|
|
361
|
+
matchedBySubfolder: manifest.providers.reduce((sum, provider) => sum + (provider.matchedBySubfolder ?? 0), 0),
|
|
362
|
+
matchedByWorktree: manifest.providers.reduce((sum, provider) => sum + (provider.matchedByWorktree ?? 0), 0),
|
|
275
363
|
},
|
|
276
364
|
};
|
|
277
365
|
}
|
|
@@ -291,12 +379,14 @@ function sourceFromDescriptor(descriptor, locator, authorizedRoot, details, dige
|
|
|
291
379
|
};
|
|
292
380
|
}
|
|
293
381
|
function providerReport(provider, status, disposition, sources, reasonCode, remediation) {
|
|
294
|
-
const timestamps = sources.map((source) => source.modifiedAt).sort();
|
|
382
|
+
const timestamps = sources.map((source) => source.modifiedAt).filter(Boolean).sort();
|
|
295
383
|
return {
|
|
296
384
|
provider,
|
|
297
385
|
status,
|
|
298
386
|
disposition,
|
|
299
387
|
sourceCount: sources.length,
|
|
388
|
+
matchedBySubfolder: 0,
|
|
389
|
+
matchedByWorktree: 0,
|
|
300
390
|
dateRange: {
|
|
301
391
|
earliest: timestamps.at(0) ?? null,
|
|
302
392
|
latest: timestamps.at(-1) ?? null,
|
|
@@ -311,6 +401,7 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
311
401
|
schemaVersion: manifest.schemaVersion,
|
|
312
402
|
workspaceId: manifest.workspaceId,
|
|
313
403
|
...(manifest.preflight ? { preflight: manifest.preflight } : {}),
|
|
404
|
+
...(manifest.authorizedRoots ? { authorizedRoots: manifest.authorizedRoots } : {}),
|
|
314
405
|
sources: manifest.sources.map((source) => ({
|
|
315
406
|
sourceId: source.sourceId,
|
|
316
407
|
provider: source.provider,
|
|
@@ -319,47 +410,84 @@ async function rediscoverManifestIdentity(manifest) {
|
|
|
319
410
|
modifiedAt: source.modifiedAt,
|
|
320
411
|
sizeBytes: source.sizeBytes,
|
|
321
412
|
digest: source.digest,
|
|
413
|
+
...(source.nativeSessionId ? { nativeSessionId: source.nativeSessionId } : {}),
|
|
414
|
+
...(source.status ? { status: source.status, exclusion: source.exclusion } : {}),
|
|
322
415
|
})),
|
|
323
416
|
providers: manifest.providers.map((provider) => ({
|
|
324
417
|
provider: provider.provider,
|
|
325
418
|
status: provider.status,
|
|
326
419
|
reasonCode: provider.reasonCode,
|
|
420
|
+
...(provider.matchedBySubfolder !== undefined ? { matchedBySubfolder: provider.matchedBySubfolder } : {}),
|
|
421
|
+
...(provider.matchedByWorktree !== undefined ? { matchedByWorktree: provider.matchedByWorktree } : {}),
|
|
422
|
+
...(provider.rootCounts ? { rootCounts: provider.rootCounts } : {}),
|
|
327
423
|
...(provider.museSourceInspections ? { museSourceInspections: provider.museSourceInspections } : {}),
|
|
328
424
|
})),
|
|
329
425
|
};
|
|
330
426
|
return sha256(JSON.stringify(identity));
|
|
331
427
|
}
|
|
332
|
-
async function
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
428
|
+
async function codexDiscoveryRoots(options) {
|
|
429
|
+
const explicit = [...(options.codexRoots ?? []), ...(options.codexRoot ? [options.codexRoot] : [])];
|
|
430
|
+
if (explicit.length > 0)
|
|
431
|
+
return [...new Set(explicit.map((root) => resolve(root)))];
|
|
432
|
+
// Shared stores require the same explicit provider-home authorization as the default root.
|
|
433
|
+
if (!options.providerHome)
|
|
434
|
+
return [];
|
|
435
|
+
const codexHome = join(resolve(options.providerHome), '.codex');
|
|
436
|
+
const roots = [join(codexHome, 'sessions')];
|
|
437
|
+
if (process.env.CODEX_HOME)
|
|
438
|
+
roots.push(resolve(process.env.CODEX_HOME, 'sessions'));
|
|
336
439
|
try {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
try {
|
|
342
|
-
value = JSON.parse(line);
|
|
343
|
-
}
|
|
344
|
-
catch {
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
const cwd = codexWorkspaceField(value);
|
|
348
|
-
if (!cwd)
|
|
349
|
-
continue;
|
|
350
|
-
try {
|
|
351
|
-
return await canonicalPath(cwd) === workspacePath;
|
|
352
|
-
}
|
|
353
|
-
catch {
|
|
354
|
-
return resolve(cwd) === workspacePath;
|
|
440
|
+
const roles = await readdir(join(codexHome, 'roles-runtime'), { withFileTypes: true });
|
|
441
|
+
for (const role of roles.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
442
|
+
if (role.isDirectory() || role.isSymbolicLink()) {
|
|
443
|
+
roots.push(join(codexHome, 'roles-runtime', role.name, '.codex', 'sessions'));
|
|
355
444
|
}
|
|
356
445
|
}
|
|
357
|
-
return false;
|
|
358
446
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
447
|
+
catch (error) {
|
|
448
|
+
if (!['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
449
|
+
throw error;
|
|
362
450
|
}
|
|
451
|
+
return [...new Set(roots)];
|
|
452
|
+
}
|
|
453
|
+
async function codexRolloutId(locator) {
|
|
454
|
+
let count = 0;
|
|
455
|
+
for await (const { line } of streamJsonLines(locator)) {
|
|
456
|
+
if (++count > 50)
|
|
457
|
+
break;
|
|
458
|
+
let value;
|
|
459
|
+
try {
|
|
460
|
+
value = JSON.parse(line);
|
|
461
|
+
}
|
|
462
|
+
catch {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const record = asObject(value);
|
|
466
|
+
const id = asObject(record.payload).id;
|
|
467
|
+
if (record.type === 'session_meta' && typeof id === 'string' && id.length > 0)
|
|
468
|
+
return id;
|
|
469
|
+
}
|
|
470
|
+
return /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i
|
|
471
|
+
.exec(basename(locator))?.[1];
|
|
472
|
+
}
|
|
473
|
+
async function codexSourceWorkspaceMatch(locator, workspacePath) {
|
|
474
|
+
let count = 0;
|
|
475
|
+
for await (const { line } of streamJsonLines(locator)) {
|
|
476
|
+
if (++count > 50)
|
|
477
|
+
break;
|
|
478
|
+
let value;
|
|
479
|
+
try {
|
|
480
|
+
value = JSON.parse(line);
|
|
481
|
+
}
|
|
482
|
+
catch {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const cwd = codexWorkspaceField(value);
|
|
486
|
+
if (!cwd)
|
|
487
|
+
continue;
|
|
488
|
+
return await matchSessionWorkspace(cwd, workspacePath);
|
|
489
|
+
}
|
|
490
|
+
return null;
|
|
363
491
|
}
|
|
364
492
|
function codexWorkspaceField(value) {
|
|
365
493
|
const root = asObject(value);
|
|
@@ -384,6 +512,43 @@ function asObject(value) {
|
|
|
384
512
|
async function canonicalPath(path) {
|
|
385
513
|
return realpath(resolve(path));
|
|
386
514
|
}
|
|
515
|
+
async function claudeSourceMatchesWorkspace(locator, workspacePath) {
|
|
516
|
+
let count = 0;
|
|
517
|
+
for await (const { line } of streamJsonLines(locator, { end: 1024 * 1024 - 1 })) {
|
|
518
|
+
if (++count > 50)
|
|
519
|
+
break;
|
|
520
|
+
let value;
|
|
521
|
+
try {
|
|
522
|
+
value = JSON.parse(line);
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const cwd = asObject(value).cwd;
|
|
528
|
+
if (typeof cwd !== 'string' || cwd.length === 0)
|
|
529
|
+
continue;
|
|
530
|
+
try {
|
|
531
|
+
return await canonicalPath(cwd) === workspacePath;
|
|
532
|
+
}
|
|
533
|
+
catch {
|
|
534
|
+
return resolve(cwd) === workspacePath;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
// Older transcripts without cwd still use the selected project folder.
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
export function claudeProjectKey(path) {
|
|
541
|
+
// Claude Code 2.1.302 replaces each character, then caps keys at 200
|
|
542
|
+
// characters with a base-36 suffix from the original path's 32-bit hash.
|
|
543
|
+
const key = path.replace(/[^A-Za-z0-9-]/g, '-');
|
|
544
|
+
if (key.length <= 200)
|
|
545
|
+
return key;
|
|
546
|
+
let hash = 0;
|
|
547
|
+
for (let index = 0; index < path.length; index += 1) {
|
|
548
|
+
hash = ((hash << 5) - hash + path.charCodeAt(index)) | 0;
|
|
549
|
+
}
|
|
550
|
+
return `${key.slice(0, 200)}-${Math.abs(hash).toString(36)}`;
|
|
551
|
+
}
|
|
387
552
|
function workspaceKey(path, retainLeadingSeparator) {
|
|
388
553
|
const normalized = path.replace(/\\/g, '/');
|
|
389
554
|
const input = retainLeadingSeparator ? normalized : normalized.replace(/^\/+/, '');
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
export const WORKSPACE_MATCH_KINDS = ['exact', 'subfolder', 'worktree'];
|
|
4
|
+
/** A nested catalog owns its subtree, even when discovery runs one workspace at a time. */
|
|
5
|
+
export async function matchSessionWorkspace(cwd, workspace) {
|
|
6
|
+
try {
|
|
7
|
+
const { path: candidate, missing } = await resolveCandidate(resolve(cwd));
|
|
8
|
+
const workspacePath = await realpath(resolve(workspace));
|
|
9
|
+
const direct = encloses(workspacePath, candidate);
|
|
10
|
+
if (missing && !direct)
|
|
11
|
+
return null;
|
|
12
|
+
const physicalCatalog = await nearestCatalogBetween(candidate, direct ? workspacePath : undefined);
|
|
13
|
+
if (physicalCatalog && !encloses(physicalCatalog, workspacePath))
|
|
14
|
+
return null;
|
|
15
|
+
const mainRepository = await worktreeMainRepository(candidate).catch(() => null);
|
|
16
|
+
// A worktree also belongs to a deeper catalog around its main repository,
|
|
17
|
+
// including when both paths sit beneath the workspace being discovered.
|
|
18
|
+
if (mainRepository && encloses(workspacePath, mainRepository)
|
|
19
|
+
&& await nearestCatalogBetween(mainRepository, workspacePath))
|
|
20
|
+
return null;
|
|
21
|
+
if (direct)
|
|
22
|
+
return candidate === workspacePath ? 'exact' : 'subfolder';
|
|
23
|
+
if (!mainRepository || !encloses(workspacePath, mainRepository))
|
|
24
|
+
return null;
|
|
25
|
+
return 'worktree';
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Unresolved paths or Git metadata cannot establish workspace ownership.
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function resolveCandidate(path) {
|
|
33
|
+
let ancestor = path;
|
|
34
|
+
const suffix = [];
|
|
35
|
+
for (;;) {
|
|
36
|
+
try {
|
|
37
|
+
// Stop at symlinks too, so realpath rejects dangling links below.
|
|
38
|
+
await lstat(ancestor);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code !== 'ENOENT')
|
|
43
|
+
throw error;
|
|
44
|
+
const parent = dirname(ancestor);
|
|
45
|
+
if (parent === ancestor)
|
|
46
|
+
throw error;
|
|
47
|
+
suffix.unshift(basename(ancestor));
|
|
48
|
+
ancestor = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { path: join(await realpath(ancestor), ...suffix), missing: suffix.length > 0 };
|
|
52
|
+
}
|
|
53
|
+
function encloses(parent, child) {
|
|
54
|
+
const suffix = relative(parent, child);
|
|
55
|
+
return suffix === '' || (!isAbsolute(suffix) && suffix !== '..' && !suffix.startsWith(`..${sep}`));
|
|
56
|
+
}
|
|
57
|
+
async function nearestCatalogBetween(candidate, stop) {
|
|
58
|
+
for (let directory = candidate; directory !== stop; directory = dirname(directory)) {
|
|
59
|
+
const catalog = await statIfPresent(join(directory, '.aiwg', 'sessions'));
|
|
60
|
+
if (catalog?.isDirectory())
|
|
61
|
+
return directory;
|
|
62
|
+
if (directory === dirname(directory))
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
async function worktreeMainRepository(candidate) {
|
|
68
|
+
for (let directory = candidate;; directory = dirname(directory)) {
|
|
69
|
+
const marker = join(directory, '.git');
|
|
70
|
+
const details = await statIfPresent(marker);
|
|
71
|
+
if (details) {
|
|
72
|
+
// Stop at the nearest repository; a normal repository or submodule
|
|
73
|
+
// must not inherit an outer worktree's ownership.
|
|
74
|
+
if (!details.isFile())
|
|
75
|
+
return null;
|
|
76
|
+
const gitfile = /^gitdir: (.+)\s*$/.exec((await readFile(marker, 'utf8')).trim());
|
|
77
|
+
if (!gitfile)
|
|
78
|
+
return null;
|
|
79
|
+
const gitdir = await realpath(resolve(directory, gitfile[1].trim()));
|
|
80
|
+
const common = (await readFile(join(gitdir, 'commondir'), 'utf8')).trim();
|
|
81
|
+
if (!common)
|
|
82
|
+
return null;
|
|
83
|
+
const commonDir = await realpath(resolve(gitdir, common));
|
|
84
|
+
const backlink = (await readFile(join(gitdir, 'gitdir'), 'utf8')).trim();
|
|
85
|
+
if (!backlink || await realpath(resolve(gitdir, backlink)) !== await realpath(marker))
|
|
86
|
+
return null;
|
|
87
|
+
// Standard linked worktrees share the main repository's .git directory.
|
|
88
|
+
if (basename(commonDir) !== '.git' || commonDir === gitdir)
|
|
89
|
+
return null;
|
|
90
|
+
const mainRepository = dirname(commonDir);
|
|
91
|
+
return await realpath(join(mainRepository, '.git')) === commonDir ? mainRepository : null;
|
|
92
|
+
}
|
|
93
|
+
if (directory === dirname(directory))
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async function statIfPresent(path) {
|
|
98
|
+
try {
|
|
99
|
+
return await stat(path);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
103
|
+
return null;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=workspace-matching.js.map
|
|
@@ -178,6 +178,7 @@ function runGit(args, options = {}) {
|
|
|
178
178
|
maxBuffer: options.maxBuffer ?? 16 * 1024 * 1024,
|
|
179
179
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
180
180
|
timeout: 120_000,
|
|
181
|
+
killSignal: 'SIGKILL',
|
|
181
182
|
});
|
|
182
183
|
if (result.error) {
|
|
183
184
|
if (result.error.code === 'ETIMEDOUT') {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
import { promises as fs } from 'node:fs';
|
|
18
18
|
import * as path from 'node:path';
|
|
19
19
|
import { projectControlPath } from '../../config/project-artifacts.js';
|
|
20
|
+
import { buildClaudeHookBlock, CLAUDE_HOOK_START, CLAUDE_HOOK_END } from './claude-hook.js';
|
|
21
|
+
import { dominantLineEnding, withLineEnding } from './line-endings.js';
|
|
20
22
|
import { buildParallelismSection, replaceOrAppendParallelismBlock } from './parallelism-section.js';
|
|
21
23
|
import { buildContextFinalizationBlock, replaceOrAppendFinalizationBlock } from './finalization.js';
|
|
22
24
|
import { buildExternalLinksSection, replaceOrAppendExternalLinksBlock, } from './external-links-section.js';
|
|
@@ -55,6 +57,15 @@ export async function buildAiwgMdContent(projectPath, stagedClaudeMdContent) {
|
|
|
55
57
|
if (claudeMdContent) {
|
|
56
58
|
// Insert the AIWG signature comment as the second line.
|
|
57
59
|
// Top line is typically `# AIWG` or `# Project` heading; preserve it.
|
|
60
|
+
// Regenerate may mirror CLAUDE.md before its hook updater runs. Normalize
|
|
61
|
+
// that managed block here too so legacy duplicate policy never reaches
|
|
62
|
+
// AIWG.md, even on the first refresh. Operator content stays untouched.
|
|
63
|
+
const start = claudeMdContent.indexOf(CLAUDE_HOOK_START);
|
|
64
|
+
const end = claudeMdContent.indexOf(CLAUDE_HOOK_END, start);
|
|
65
|
+
if (start >= 0 && end >= start) {
|
|
66
|
+
const hook = withLineEnding(buildClaudeHookBlock(), dominantLineEnding(claudeMdContent));
|
|
67
|
+
claudeMdContent = claudeMdContent.slice(0, start) + hook + claudeMdContent.slice(end + CLAUDE_HOOK_END.length);
|
|
68
|
+
}
|
|
58
69
|
const lines = claudeMdContent.split('\n');
|
|
59
70
|
// #1268: strip `@AIWG.md` self-include directives. CLAUDE.md uses
|
|
60
71
|
// `@AIWG.md` to pull in the framework context; AIWG.md IS that framework
|
|
@@ -26,29 +26,14 @@ import { dominantLineEnding, withLineEnding } from './line-endings.js';
|
|
|
26
26
|
import { assertSafeContextFile } from './context-file-safety.js';
|
|
27
27
|
export const CLAUDE_HOOK_START = '<!-- AIWG:claude-md-hook:start -->';
|
|
28
28
|
export const CLAUDE_HOOK_END = '<!-- AIWG:claude-md-hook:end -->';
|
|
29
|
-
function
|
|
30
|
-
const providerNative = policy.provider_native ?? 'explicit-only';
|
|
31
|
-
const design = policy.destinations?.['claude-code.design'];
|
|
32
|
-
const designEnabled = design?.enabled !== false && design?.use_when !== 'disabled';
|
|
33
|
-
return [
|
|
34
|
-
'## AIWG artifact output policy',
|
|
35
|
-
'',
|
|
36
|
-
`- Canonical durable artifacts: AIWG artifact store (policy: ${policy.canonical ?? 'aiwg'}).`,
|
|
37
|
-
`- Provider-native presentation/export: ${providerNative}.`,
|
|
38
|
-
`- Claude Design: ${designEnabled ? 'available only when explicitly selected by the resolved policy or requested by the user' : 'disabled by project policy'}.`,
|
|
39
|
-
'- Never substitute, relocate, or omit the canonical AIWG plan/review artifact because Claude adds or changes a provider default.',
|
|
40
|
-
'- When both canonical and presentation outputs are selected, write the canonical artifact first; treat Design as a derived export and record provenance linking it to the canonical source of truth.',
|
|
41
|
-
'- Unknown provider-native destinations fail safe with a diagnostic. Higher-authority project policy overrides user preferences; explicit task selection overrides provider defaults only within the project policy ceiling.',
|
|
42
|
-
];
|
|
43
|
-
}
|
|
44
|
-
export function buildClaudeHookBlock(policy = {}) {
|
|
29
|
+
export function buildClaudeHookBlock(_policy = {}) {
|
|
45
30
|
return [
|
|
46
31
|
CLAUDE_HOOK_START,
|
|
47
32
|
'',
|
|
48
33
|
buildProviderBootstrapBlock('claude'),
|
|
49
34
|
'@.aiwg/aiwg.config',
|
|
50
35
|
'',
|
|
51
|
-
|
|
36
|
+
'Artifact destination authority: `aiwg show rule artifact-output-policy` (kernel).',
|
|
52
37
|
'',
|
|
53
38
|
'<!--',
|
|
54
39
|
' This block is managed by `aiwg regenerate` and `aiwg use`.',
|
|
@@ -72,15 +57,7 @@ export async function ensureClaudeMdHook(projectPath, opts = {}) {
|
|
|
72
57
|
action: 'skipped',
|
|
73
58
|
warnings: [],
|
|
74
59
|
};
|
|
75
|
-
let
|
|
76
|
-
try {
|
|
77
|
-
const config = JSON.parse(await fs.readFile(path.join(projectPath, '.aiwg', 'aiwg.config'), 'utf8'));
|
|
78
|
-
policy = config.artifact_outputs ?? {};
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Missing/legacy/temporarily malformed config receives the safe default.
|
|
82
|
-
}
|
|
83
|
-
let block = buildClaudeHookBlock(policy);
|
|
60
|
+
let block = buildClaudeHookBlock();
|
|
84
61
|
// Case 1: CLAUDE.md does not exist — create a minimal one with just the block.
|
|
85
62
|
let existing;
|
|
86
63
|
try {
|