@uniqbit/mate-core 0.15.5 → 0.16.0-canary.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/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/claude-plugin/hooks/hooks.json +3 -6
- package/claude-plugin/hooks/session-guidance.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +19 -0
- package/package.json +6 -4
- package/src/cli/commands/artifact/artifact.ts +19 -3
- package/src/cli/commands/artifact/finish/command.ts +183 -57
- package/src/cli/commands/artifact/finish/engine.ts +80 -91
- package/src/cli/commands/artifact/finish/finisher.ts +26 -33
- package/src/cli/commands/artifact/finish/git.ts +34 -23
- package/src/cli/commands/artifact/finish/index.ts +9 -3
- package/src/cli/commands/artifact/finish/openspec.ts +225 -97
- package/src/cli/commands/artifact/pending/command.ts +175 -0
- package/src/cli/commands/artifact/pending/discovery.ts +249 -0
- package/src/cli/commands/artifact/pending/index.ts +17 -0
- package/src/cli/commands/cap/index-cmd.ts +9 -1
- package/src/cli/commands/cap/index.ts +2 -6
- package/src/cli/commands/companion/companion.ts +5 -1
- package/src/cli/commands/companion/link.ts +2 -2
- package/src/cli/commands/companion/sync.ts +92 -0
- package/src/cli/commands/doctor.ts +0 -3
- package/src/cli/commands/launch/shared.ts +23 -5
- package/src/cli/commands/report/collector.ts +72 -78
- package/src/cli/commands/report/contract.ts +40 -1
- package/src/cli/commands/report/highlight.ts +27 -0
- package/src/cli/commands/report/index.ts +11 -18
- package/src/cli/commands/report/renderer.ts +199 -2
- package/src/cli/commands/report/types.ts +26 -1
- package/src/cli/commands/shared/companion-selection.ts +107 -10
- package/src/cli/commands/studio/areas.ts +68 -0
- package/src/cli/commands/studio/index.ts +69 -0
- package/src/cli/commands/studio/inventory.ts +55 -0
- package/src/cli/commands/studio/mate-inventory.ts +43 -0
- package/src/cli/commands/studio/openspec-cli.ts +198 -0
- package/src/cli/commands/studio/payload.ts +184 -0
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +61 -0
- package/src/cli/commands/studio/server.ts +201 -0
- package/src/cli/commands/studio/snapshot.ts +63 -0
- package/src/cli/commands/studio/topology.ts +199 -0
- package/src/cli/commands/studio/views/client.ts +197 -0
- package/src/cli/commands/studio/views/companion-picker.tsx +91 -0
- package/src/cli/commands/studio/views/companion-selector.tsx +90 -0
- package/src/cli/commands/studio/views/dashboard/changes.tsx +107 -0
- package/src/cli/commands/studio/views/dashboard/index.tsx +19 -0
- package/src/cli/commands/studio/views/document.tsx +256 -0
- package/src/cli/commands/studio/views/error.tsx +20 -0
- package/src/cli/commands/studio/views/model.ts +34 -0
- package/src/cli/commands/studio/views/pairings.tsx +38 -0
- package/src/cli/commands/studio/views/skills/index.tsx +87 -0
- package/src/cli/commands/studio/views/specs/index.tsx +101 -0
- package/src/cli/commands/studio/views/styles.ts +389 -0
- package/src/cli/commands/studio/views/warnings.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/steps.ts +329 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +190 -0
- package/src/cli/commands/unwrap.ts +70 -0
- package/src/cli/commands/wrap.ts +164 -0
- package/src/cli/main.ts +68 -19
- package/src/cli/parse-flags.ts +36 -11
- package/src/cli/usage.ts +12 -3
- package/src/framework.ts +1 -7
- package/src/hooks/session-banner.ts +64 -11
- package/src/hooks/session-guidance.ts +40 -0
- package/src/hooks/validate-artifact-path.ts +108 -35
- package/src/lib/fs-utils.ts +9 -0
- package/src/lib/install.ts +33 -0
- package/src/lib/orchestrator/adapters/base.ts +14 -125
- package/src/lib/orchestrator/adapters/claude.ts +0 -11
- package/src/lib/orchestrator/adapters/opencode.ts +2 -32
- package/src/lib/orchestrator/companion-git-sync.ts +94 -84
- package/src/lib/orchestrator/config-store.ts +2 -21
- package/src/lib/orchestrator/editor.ts +12 -22
- package/src/lib/orchestrator/framework-context.ts +17 -6
- package/src/lib/orchestrator/global-config-store.ts +1 -1
- package/src/lib/orchestrator/launcher.ts +97 -7
- package/src/lib/orchestrator/opencode-guidance.ts +4 -56
- package/src/lib/orchestrator/projection-claude-entry.ts +198 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +120 -0
- package/src/lib/orchestrator/projection-companion-link.ts +62 -0
- package/src/lib/orchestrator/projection-entries.ts +377 -0
- package/src/lib/orchestrator/projection-record.ts +56 -0
- package/src/lib/orchestrator/projection-runtime-documents.ts +424 -0
- package/src/lib/orchestrator/projection-types.ts +169 -0
- package/src/lib/orchestrator/repo-local-registry.ts +37 -133
- package/src/lib/orchestrator/repo-local-store.ts +96 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -9
- package/src/lib/orchestrator/types.ts +1 -0
- package/src/lib/orchestrator/working-repo-projection.ts +366 -0
- package/src/lib/orchestrator/workspace-inventory.ts +1 -1
- package/src/lib/package-paths.ts +11 -1
- package/src/opencode/companion-hooks.ts +89 -245
- package/src/opencode/companion-policy.ts +35 -10
- package/src/opencode/index.ts +1 -0
- package/src/opencode/projected-guidance.ts +56 -0
- package/src/opencode/tui.tsx +13 -4
- package/src/playbooks/companion-guidance.ts +32 -116
- package/src/plugins.ts +0 -1
- package/src/runtime/companion-git-state.ts +156 -0
- package/src/runtime/companion-git.ts +203 -0
- package/src/runtime/companion-guidance.ts +222 -0
- package/src/runtime/companion-sync.ts +298 -0
- package/src/runtime/env-names.ts +30 -0
- package/src/runtime/env.ts +67 -35
- package/src/runtime/framework.ts +10 -0
- package/src/runtime/freshness.ts +58 -0
- package/src/runtime/index.ts +104 -0
- package/src/runtime/install.ts +30 -0
- package/src/runtime/policy.ts +66 -0
- package/src/runtime/projected-guidance.ts +45 -0
- package/src/runtime/projection.ts +224 -0
- package/src/runtime/repo-local.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +56 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +48 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/tasks.md +22 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +31 -90
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/design.md +3 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/explore-brief.md +6 -6
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/tasks.md +3 -0
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +30 -0
- package/src/templates/capabilities/react-doctor/claude/hooks/react-doctor.sh +2 -2
- package/src/templates/mate-skills/agents/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/agents/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/agents/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/agents/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +147 -0
- package/src/templates/{capabilities/openspec-cap/mate-skills → mate-skills}/agents/mate-openspec-backfill/SKILL.md +4 -2
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/claude/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/claude/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +147 -0
- package/src/templates/mate-skills/claude/mate-openspec-backfill/SKILL.md +67 -0
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/report-assets/README.md +32 -0
- package/src/templates/report-assets/mermaid.LICENSE +21 -0
- package/src/templates/report-assets/mermaid.min.js +4376 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +1 -9
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +1476 -197
- package/src/tools/setup/capabilities/graphify.ts +16 -7
- package/src/tools/setup/capabilities/openspec.ts +63 -56
- package/src/tools/setup/capabilities/tokensave.ts +47 -0
- package/src/tools/setup/engine.ts +34 -6
- package/src/tools/setup/mate.ts +42 -13
- package/src/tools/setup/plugin.ts +9 -0
- package/src/tools/setup/plugins/guidance.ts +11 -1
- package/src/tools/setup/providers/claude-format.ts +49 -4
- package/src/tools/setup/providers/claude-plugin-hooks.ts +117 -0
- package/src/tools/setup/providers/claude.ts +55 -220
- package/src/tools/setup/providers/opencode.ts +41 -14
- package/src/tools/setup/runtime-documents.ts +174 -0
- package/src/tools/setup/surface-target.ts +50 -0
- package/src/tools/setup/working-repo-cleanup.ts +33 -26
- package/src/tools/setup/working-repo-local-state.ts +21 -1
- package/src/tools/setup.ts +25 -3
- package/wrappers/bin/graphify +57 -8
- package/wrappers/bin/openspec +50 -3
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +0 -8
- package/src/cli/commands/cap/headroom.ts +0 -52
- package/src/cli/commands/workspace/list.ts +0 -25
- package/src/cli/commands/workspace/materialize.ts +0 -46
- package/src/cli/commands/workspace/workspace.ts +0 -22
- package/src/hooks/artifact-finish-nudge.ts +0 -244
- package/src/lib/orchestrator/headroom/proxy.ts +0 -116
- package/src/lib/orchestrator/workspace-materialize.ts +0 -80
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +0 -51
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +0 -134
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +0 -58
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +0 -139
- package/src/tools/setup/capabilities/headroom.ts +0 -57
- /package/src/cli/commands/{workspace → companion}/open.ts +0 -0
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { FRAMEWORK_NAME } from "../../framework";
|
|
5
|
+
import { readProjectionFile } from "../../runtime/projection";
|
|
6
|
+
import { repoLocalDirPath } from "../../runtime/repo-local";
|
|
7
|
+
import { companionGitSyncDeps } from "./companion-git-sync";
|
|
8
|
+
import type { GlobalConfigStore } from "./global-config-store";
|
|
9
|
+
import { projectionEntries } from "./projection-entries";
|
|
10
|
+
import {
|
|
11
|
+
isExternalDocument,
|
|
12
|
+
recordedRuntimeDocuments,
|
|
13
|
+
removeRuntimeDocuments,
|
|
14
|
+
} from "./projection-runtime-documents";
|
|
15
|
+
import {
|
|
16
|
+
firstFailure,
|
|
17
|
+
type RenderedRuntimeDocument,
|
|
18
|
+
type ProjectionDescription,
|
|
19
|
+
type ProjectionEntry,
|
|
20
|
+
type ProjectionEntryOutcome,
|
|
21
|
+
type ProjectionEntryPresence,
|
|
22
|
+
type ProjectionInput,
|
|
23
|
+
type ProjectionRemovalInput,
|
|
24
|
+
type ProjectionRemovalResult,
|
|
25
|
+
type ProjectionResult,
|
|
26
|
+
type ProjectionScope,
|
|
27
|
+
} from "./projection-types";
|
|
28
|
+
import type { FrameworkConfig, LinkedRepository } from "./types";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The owner of the Managed Projection: every path Mate places inside a Working
|
|
32
|
+
* Repository is written and removed from here, through the declarations in
|
|
33
|
+
* `projection-entries.ts`. This module holds no Agent Runtime format knowledge —
|
|
34
|
+
* that is what keeps a second runtime writing the same destination a new
|
|
35
|
+
* declaration rather than a branch in here.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export type ProjectionWriteResult =
|
|
39
|
+
| { kind: "written"; projectionRoot: string; companionPath: string }
|
|
40
|
+
| { kind: "current"; projectionRoot: string; companionPath: string }
|
|
41
|
+
| { kind: "failed"; error: Error };
|
|
42
|
+
|
|
43
|
+
function toError(error: unknown): Error {
|
|
44
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function outcome(
|
|
48
|
+
entry: ProjectionEntry,
|
|
49
|
+
state: ProjectionEntryOutcome["state"],
|
|
50
|
+
error?: Error,
|
|
51
|
+
): ProjectionEntryOutcome {
|
|
52
|
+
return {
|
|
53
|
+
id: entry.id,
|
|
54
|
+
path: entry.path,
|
|
55
|
+
kind: entry.kind,
|
|
56
|
+
state,
|
|
57
|
+
...(error ? { error } : {}),
|
|
58
|
+
...(entry.degradable ? { degradable: true } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Writes the entries the scope declares, in catalogue order so the managed
|
|
64
|
+
* exclude block precedes anything it covers. Best-effort per entry: one failure
|
|
65
|
+
* neither aborts the rest nor throws, so a caller that must fail reads the
|
|
66
|
+
* reported outcomes instead.
|
|
67
|
+
*/
|
|
68
|
+
export async function project(
|
|
69
|
+
scope: ProjectionScope,
|
|
70
|
+
input: ProjectionInput,
|
|
71
|
+
): Promise<ProjectionResult> {
|
|
72
|
+
const outcomes: ProjectionEntryOutcome[] = [];
|
|
73
|
+
for (const entry of projectionEntries()) {
|
|
74
|
+
if (!entry.scopes.includes(scope)) continue;
|
|
75
|
+
try {
|
|
76
|
+
// oxlint-disable-next-line no-await-in-loop -- catalogue order is the contract
|
|
77
|
+
outcomes.push(outcome(entry, await entry.write(input)));
|
|
78
|
+
} catch (error) {
|
|
79
|
+
outcomes.push(outcome(entry, "failed", toError(error)));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { root: repoLocalDirPath(input.repoPath), scope, outcomes };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Removes every declared entry regardless of which scope wrote it, walking the
|
|
87
|
+
* catalogue in reverse. An entry covered by another entry's removal, or
|
|
88
|
+
* deliberately retained, is reported as such rather than being silently absent.
|
|
89
|
+
*/
|
|
90
|
+
export async function unproject(input: ProjectionRemovalInput): Promise<ProjectionRemovalResult> {
|
|
91
|
+
const outcomes: ProjectionEntryOutcome[] = [];
|
|
92
|
+
for (const entry of [...projectionEntries()].reverse()) {
|
|
93
|
+
const { removal } = entry;
|
|
94
|
+
if (removal.by === "entry") {
|
|
95
|
+
outcomes.push(outcome(entry, "covered"));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (removal.by === "retained") {
|
|
99
|
+
outcomes.push(outcome(entry, "retained"));
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
// oxlint-disable-next-line no-await-in-loop -- reverse catalogue order is the contract
|
|
104
|
+
outcomes.push(outcome(entry, await removal.remove(input)));
|
|
105
|
+
} catch (error) {
|
|
106
|
+
outcomes.push(outcome(entry, "failed", toError(error)));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { root: repoLocalDirPath(input.repoPath), outcomes };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Removes entries that wrap owns but an unwrap must withdraw independently. */
|
|
113
|
+
export async function removeWorkingRepositoryUnwrapEntries(
|
|
114
|
+
repoPath: string,
|
|
115
|
+
): Promise<ProjectionEntryOutcome[]> {
|
|
116
|
+
const entries = projectionEntries().filter(
|
|
117
|
+
(entry) => entry.removeOnUnwrap && entry.removal.by === "self",
|
|
118
|
+
);
|
|
119
|
+
return Promise.all(
|
|
120
|
+
entries.map(async (entry) => {
|
|
121
|
+
const removal = entry.removal;
|
|
122
|
+
if (removal.by !== "self") return outcome(entry, "failed", new Error("invalid removal"));
|
|
123
|
+
try {
|
|
124
|
+
return outcome(entry, await removal.remove({ repoPath }));
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return outcome(entry, "failed", toError(error));
|
|
127
|
+
}
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Reads and reports; writes nothing. Freshness is a verdict above this module. */
|
|
133
|
+
export async function describe(repoPath: string): Promise<ProjectionDescription> {
|
|
134
|
+
const entries: ProjectionEntryPresence[] = [];
|
|
135
|
+
for (const entry of projectionEntries()) {
|
|
136
|
+
const present = entry.present
|
|
137
|
+
? // oxlint-disable-next-line no-await-in-loop -- reported in catalogue order
|
|
138
|
+
await entry.present(repoPath)
|
|
139
|
+
: await fs
|
|
140
|
+
.access(path.join(path.resolve(repoPath), entry.path))
|
|
141
|
+
.then(() => true)
|
|
142
|
+
.catch(() => false);
|
|
143
|
+
entries.push({ id: entry.id, path: entry.path, kind: entry.kind, present });
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
root: repoLocalDirPath(repoPath),
|
|
147
|
+
entries,
|
|
148
|
+
projection: readProjectionFile(repoPath),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Makes the Managed Projection durable at the Working Repository's Projection
|
|
154
|
+
* Root so an Unmanaged Session can resolve it from files. Reports its outcome
|
|
155
|
+
* rather than swallowing it: callers that asked for the write explicitly need
|
|
156
|
+
* to fail on it, while resolution paths stay best-effort through the wrapper
|
|
157
|
+
* below. Callers must have established that the companion is not a guess.
|
|
158
|
+
*/
|
|
159
|
+
export async function projectWorkingRepository(
|
|
160
|
+
companionPath: string,
|
|
161
|
+
repository: LinkedRepository,
|
|
162
|
+
): Promise<ProjectionWriteResult> {
|
|
163
|
+
const resolvedCompanionPath = path.resolve(companionPath);
|
|
164
|
+
const result = await project("session", {
|
|
165
|
+
repoPath: repository.path,
|
|
166
|
+
companionPath: resolvedCompanionPath,
|
|
167
|
+
repository,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const failure = firstFailure(result.outcomes);
|
|
171
|
+
if (failure) return { kind: "failed", error: failure.error ?? new Error("projection failed") };
|
|
172
|
+
|
|
173
|
+
/** The projection pair is what "the projection" means to these callers. */
|
|
174
|
+
const pair = result.outcomes.find((candidate) => candidate.id === "projection-pair");
|
|
175
|
+
return {
|
|
176
|
+
kind: pair?.state === "written" ? "written" : "current",
|
|
177
|
+
projectionRoot: result.root,
|
|
178
|
+
companionPath: resolvedCompanionPath,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The rendered runtime documents, placed. Reported per document because the
|
|
184
|
+
* operator asked for these by name: a failure has to say which one failed while
|
|
185
|
+
* the projection already written stays where it is.
|
|
186
|
+
*/
|
|
187
|
+
export type RuntimeDocumentsResult =
|
|
188
|
+
| { kind: "written" | "current"; documents: string[] }
|
|
189
|
+
| { kind: "failed"; document: string; error: Error };
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Which of the given repo-relative paths Git already has in its index. Asked
|
|
193
|
+
* through the same runner the companion sync uses rather than a second spawn.
|
|
194
|
+
* Best-effort by construction: a directory that is no repository, a Git that is
|
|
195
|
+
* not installed, and a `ls-files` that fails for any other reason all answer
|
|
196
|
+
* "nothing tracked" — this feeds a warning, and a warning may not fail a wrap.
|
|
197
|
+
*/
|
|
198
|
+
async function trackedDocuments(repoPath: string, documentPaths: string[]): Promise<string[]> {
|
|
199
|
+
if (documentPaths.length === 0) return [];
|
|
200
|
+
try {
|
|
201
|
+
const result = await companionGitSyncDeps.runGit(
|
|
202
|
+
["ls-files", "-z", "--", ...documentPaths],
|
|
203
|
+
path.resolve(repoPath),
|
|
204
|
+
);
|
|
205
|
+
if (result.status !== 0) return [];
|
|
206
|
+
return result.stdout.split("\0").filter(Boolean);
|
|
207
|
+
} catch {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The managed exclude block only hides a file Git does not already track, so a
|
|
214
|
+
* repository that commits one of these documents keeps it in the index and the
|
|
215
|
+
* regions land in a tracked file. Those regions carry machine-absolute
|
|
216
|
+
* companion paths — keys naming directories under this user's home — so a
|
|
217
|
+
* routine `git add -A` pushes one machine's layout to everyone else. Warn and
|
|
218
|
+
* write anyway: refusing would silently drop the projection the repository was
|
|
219
|
+
* wrapped for.
|
|
220
|
+
*
|
|
221
|
+
* Asked here rather than while rendering because the render also runs on every
|
|
222
|
+
* launch; the wrap pass is the one that is a deliberate act, so this is the
|
|
223
|
+
* scope where saying it once is information rather than noise.
|
|
224
|
+
*/
|
|
225
|
+
async function warnAboutTrackedDocuments(
|
|
226
|
+
repository: LinkedRepository,
|
|
227
|
+
runtimeDocuments: readonly RenderedRuntimeDocument[],
|
|
228
|
+
): Promise<void> {
|
|
229
|
+
const tracked = await trackedDocuments(
|
|
230
|
+
repository.path,
|
|
231
|
+
runtimeDocuments
|
|
232
|
+
.map((document) => document.path)
|
|
233
|
+
.filter((documentPath) => !isExternalDocument(documentPath)),
|
|
234
|
+
);
|
|
235
|
+
for (const documentPath of tracked) {
|
|
236
|
+
console.error(
|
|
237
|
+
`${FRAMEWORK_NAME}: warning: ${documentPath} is tracked by Git in ${repository.id}; ${FRAMEWORK_NAME} writes machine-absolute companion paths into it, so committing it would push this machine's paths to everyone else on the repository.`,
|
|
238
|
+
);
|
|
239
|
+
console.error(
|
|
240
|
+
`Untrack it with \`git rm --cached ${documentPath}\` if those paths should stay local.`,
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function projectWorkingRuntimeDocuments(
|
|
246
|
+
companionPath: string,
|
|
247
|
+
repository: LinkedRepository,
|
|
248
|
+
config: FrameworkConfig,
|
|
249
|
+
runtimeDocuments: readonly RenderedRuntimeDocument[],
|
|
250
|
+
globalConfigStore?: GlobalConfigStore,
|
|
251
|
+
): Promise<RuntimeDocumentsResult> {
|
|
252
|
+
await warnAboutTrackedDocuments(repository, runtimeDocuments);
|
|
253
|
+
const result = await project("wrap", {
|
|
254
|
+
repoPath: repository.path,
|
|
255
|
+
companionPath: path.resolve(companionPath),
|
|
256
|
+
repository,
|
|
257
|
+
config,
|
|
258
|
+
runtimeDocuments,
|
|
259
|
+
globalConfigStore,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const failure = firstFailure(result.outcomes);
|
|
263
|
+
if (failure) {
|
|
264
|
+
return {
|
|
265
|
+
kind: "failed",
|
|
266
|
+
document: failure.path,
|
|
267
|
+
error: failure.error ?? new Error("runtime document write failed"),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** The destinations the caller handed over; the rest of the pass is scaffolding. */
|
|
272
|
+
const destinations = new Set(
|
|
273
|
+
runtimeDocuments.map((document) => document.path.split("/").join(path.sep)),
|
|
274
|
+
);
|
|
275
|
+
const outcomes = result.outcomes.filter((outcome) => destinations.has(outcome.path));
|
|
276
|
+
return {
|
|
277
|
+
kind: outcomes.some((outcome) => outcome.state === "written") ? "written" : "current",
|
|
278
|
+
documents: [...new Set(outcomes.map((outcome) => outcome.path))],
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Whether `mate wrap` configured this Working Repository. Asked of the runtime
|
|
284
|
+
* document manifest rather than of the Projection Root, because the root is not
|
|
285
|
+
* evidence of a wrap: a launch and every Capability command write the
|
|
286
|
+
* projection pair too. The manifest is written only by a pass that placed a
|
|
287
|
+
* runtime document, and wrapping is the only pass that places one.
|
|
288
|
+
*/
|
|
289
|
+
export async function isWorkingRepositoryWrapped(repoPath: string): Promise<boolean> {
|
|
290
|
+
return (await recordedRuntimeDocuments(repoPath)).length > 0;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export type UnwrapResult =
|
|
294
|
+
| { kind: "unwrapped" | "absent"; documents: string[] }
|
|
295
|
+
| { kind: "failed"; document: string; error: Error };
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* The inverse of {@link projectWorkingRuntimeDocuments}: runtime documents and
|
|
299
|
+
* projected Claude skill links are withdrawn while the Projection Root, the
|
|
300
|
+
* Repository Link and the companion link are left exactly as they were. That
|
|
301
|
+
* asymmetry with `mate working cleanup` is the point — cleanup removes Mate's
|
|
302
|
+
* whole local integration including the Repository Link, while unwrapping takes
|
|
303
|
+
* back only what wrapping added, leaving a linked repository that was never
|
|
304
|
+
* wrapped. That is what makes unwrapping the way back to a Managed Session
|
|
305
|
+
* rather than a teardown.
|
|
306
|
+
*
|
|
307
|
+
* Driven by the manifest, so it withdraws whatever the wrap that ran recorded,
|
|
308
|
+
* including a destination this release no longer renders.
|
|
309
|
+
*/
|
|
310
|
+
export async function unwrapWorkingRuntimeDocuments(repoPath: string): Promise<UnwrapResult> {
|
|
311
|
+
const documents = await recordedRuntimeDocuments(repoPath);
|
|
312
|
+
const removeWrapEntries = async (): Promise<ProjectionEntryOutcome[]> =>
|
|
313
|
+
removeWorkingRepositoryUnwrapEntries(repoPath);
|
|
314
|
+
if (documents.length === 0) {
|
|
315
|
+
const outcomes = await removeWrapEntries();
|
|
316
|
+
const failure = firstFailure(outcomes);
|
|
317
|
+
if (failure) {
|
|
318
|
+
return {
|
|
319
|
+
kind: "failed",
|
|
320
|
+
document: failure.path,
|
|
321
|
+
error: failure.error ?? new Error("wrap entry removal failed"),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return outcomes.some((entry) => entry.state === "removed")
|
|
325
|
+
? { kind: "unwrapped", documents: [] }
|
|
326
|
+
: { kind: "absent", documents: [] };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* One manifest read and one manifest write for the whole withdrawal. A
|
|
331
|
+
* per-document call could not be run concurrently — each rewrites the whole
|
|
332
|
+
* manifest, so the last write would restore the keys the others removed, and
|
|
333
|
+
* a surviving key reads back as still wrapped — leaving a launch refused after
|
|
334
|
+
* a successful unwrap.
|
|
335
|
+
*/
|
|
336
|
+
const { removed, error } = await removeRuntimeDocuments(repoPath, documents);
|
|
337
|
+
/** Reported after the successful withdrawals are already durable. */
|
|
338
|
+
if (error) return { kind: "failed", document: error.document, error: error.error };
|
|
339
|
+
const outcomes = await removeWrapEntries();
|
|
340
|
+
const failure = firstFailure(outcomes);
|
|
341
|
+
if (failure) {
|
|
342
|
+
return {
|
|
343
|
+
kind: "failed",
|
|
344
|
+
document: failure.path,
|
|
345
|
+
error: failure.error ?? new Error("wrap entry removal failed"),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
return { kind: "unwrapped", documents: removed };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Best-effort like `backfillCompanionRegistration`: a read-only Working
|
|
353
|
+
* Repository warns and the caller still proceeds.
|
|
354
|
+
*/
|
|
355
|
+
export async function projectWorkingRepositoryBestEffort(
|
|
356
|
+
companionPath: string,
|
|
357
|
+
repository: LinkedRepository,
|
|
358
|
+
): Promise<ProjectionWriteResult> {
|
|
359
|
+
const result = await projectWorkingRepository(companionPath, repository);
|
|
360
|
+
if (result.kind === "failed") {
|
|
361
|
+
console.error(
|
|
362
|
+
`${FRAMEWORK_NAME}: warning: failed to write the projection for ${repository.id}: ${result.error.message}`,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
@@ -5,7 +5,7 @@ import { readCompanionRegistry } from "./companion-registry-reader";
|
|
|
5
5
|
import { GlobalConfigStore } from "./global-config-store";
|
|
6
6
|
import type { LinkedRepository } from "./types";
|
|
7
7
|
|
|
8
|
-
/** Envelope version for
|
|
8
|
+
/** Envelope version for the aggregate inventory shape; bump on any breaking shape change. */
|
|
9
9
|
export const WORKSPACE_INVENTORY_SCHEMA_VERSION = 1;
|
|
10
10
|
|
|
11
11
|
export type CompanionInventoryHealth = "ready" | "missing" | "unreadable";
|
package/src/lib/package-paths.ts
CHANGED
|
@@ -3,9 +3,19 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { getActiveDistribution } from "../distribution";
|
|
6
|
+
import { mateInstallPath } from "../runtime/install";
|
|
6
7
|
|
|
7
8
|
const require = createRequire(import.meta.url);
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Root of the installed mate-core package. A stamp input for the durable
|
|
12
|
+
* projection: a mate installed at a different path resolves different wrapper
|
|
13
|
+
* and plugin assets.
|
|
14
|
+
*/
|
|
15
|
+
export function getMateInstallPath(): string {
|
|
16
|
+
return mateInstallPath();
|
|
17
|
+
}
|
|
18
|
+
|
|
9
19
|
/**
|
|
10
20
|
* Resolved wrapper directory: a distribution asset root that ships
|
|
11
21
|
* `wrappers/bin` wins over core's bundled default.
|
|
@@ -35,7 +45,7 @@ export function getClaudePluginRoot(): string {
|
|
|
35
45
|
export const CLAUDE_PLUGIN_HOOK_SHIMS = [
|
|
36
46
|
"validate-artifact-path.mjs",
|
|
37
47
|
"session-banner.mjs",
|
|
38
|
-
"
|
|
48
|
+
"session-guidance.mjs",
|
|
39
49
|
] as const;
|
|
40
50
|
|
|
41
51
|
/**
|