@basou/core 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1104 -10
- package/dist/index.js +965 -54
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/schemas/event.schema.json +7 -0
- package/schemas/manifest.schema.json +92 -9
- package/schemas/session-import.schema.json +7 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { SimpleGit } from 'simple-git';
|
|
2
3
|
import { ChildProcess } from 'node:child_process';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -50,6 +51,16 @@ declare function summarizeAdapterOutput(_stream: "stdout" | "stderr", _raw: stri
|
|
|
50
51
|
* capabilities, approval policy, adapter config, and git policy. The
|
|
51
52
|
* `adapters."claude-code"` key uses a hyphen; downstream code accesses it
|
|
52
53
|
* via bracket notation.
|
|
54
|
+
*
|
|
55
|
+
* Every object here is `looseObject` (NOT the default strip), so unknown keys
|
|
56
|
+
* at every level survive parse. The manifest is the declarative source of truth
|
|
57
|
+
* and is git-tracked and read-modify-written by `basou project` commands; with
|
|
58
|
+
* the default strip, a field this basou does not recognize — a newer version's
|
|
59
|
+
* additive field, a future adapter under `adapters`, a hand-added key — would be
|
|
60
|
+
* silently dropped on the next write. Preserving them keeps basou from destroying
|
|
61
|
+
* config it does not understand (forward-compatible), while known fields are still
|
|
62
|
+
* fully type-checked and validated. {@link unknownManifestKeys} surfaces the
|
|
63
|
+
* unrecognized top-level keys so preservation is not silent.
|
|
53
64
|
*/
|
|
54
65
|
declare const ManifestSchema: z.ZodObject<{
|
|
55
66
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
@@ -59,15 +70,16 @@ declare const ManifestSchema: z.ZodObject<{
|
|
|
59
70
|
name: z.ZodString;
|
|
60
71
|
created_at: z.ZodString;
|
|
61
72
|
updated_at: z.ZodString;
|
|
62
|
-
|
|
73
|
+
view: z.ZodOptional<z.ZodString>;
|
|
74
|
+
}, z.core.$loose>;
|
|
63
75
|
project: z.ZodObject<{
|
|
64
76
|
name: z.ZodOptional<z.ZodString>;
|
|
65
77
|
description: z.ZodOptional<z.ZodString>;
|
|
66
78
|
repository_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
67
|
-
}, z.core.$
|
|
79
|
+
}, z.core.$loose>;
|
|
68
80
|
capabilities: z.ZodObject<{
|
|
69
81
|
enabled: z.ZodArray<z.ZodString>;
|
|
70
|
-
}, z.core.$
|
|
82
|
+
}, z.core.$loose>;
|
|
71
83
|
approval: z.ZodObject<{
|
|
72
84
|
required_for: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
73
85
|
default_risk_level: z.ZodEnum<{
|
|
@@ -76,25 +88,63 @@ declare const ManifestSchema: z.ZodObject<{
|
|
|
76
88
|
high: "high";
|
|
77
89
|
critical: "critical";
|
|
78
90
|
}>;
|
|
79
|
-
}, z.core.$
|
|
91
|
+
}, z.core.$loose>;
|
|
80
92
|
adapters: z.ZodObject<{
|
|
81
93
|
"claude-code": z.ZodObject<{
|
|
82
94
|
enabled: z.ZodBoolean;
|
|
83
95
|
config_path: z.ZodOptional<z.ZodString>;
|
|
84
|
-
}, z.core.$
|
|
85
|
-
}, z.core.$
|
|
96
|
+
}, z.core.$loose>;
|
|
97
|
+
}, z.core.$loose>;
|
|
86
98
|
git: z.ZodObject<{
|
|
87
99
|
events_log: z.ZodDefault<z.ZodEnum<{
|
|
88
100
|
ignore: "ignore";
|
|
89
101
|
commit: "commit";
|
|
90
102
|
}>>;
|
|
91
|
-
}, z.core.$
|
|
103
|
+
}, z.core.$loose>;
|
|
92
104
|
import: z.ZodOptional<z.ZodObject<{
|
|
93
105
|
source_roots: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
94
|
-
}, z.core.$
|
|
95
|
-
|
|
106
|
+
}, z.core.$loose>>;
|
|
107
|
+
repos: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
108
|
+
path: z.ZodString;
|
|
109
|
+
visibility: z.ZodOptional<z.ZodEnum<{
|
|
110
|
+
public: "public";
|
|
111
|
+
private: "private";
|
|
112
|
+
"future-public": "future-public";
|
|
113
|
+
}>>;
|
|
114
|
+
language: z.ZodOptional<z.ZodEnum<{
|
|
115
|
+
en: "en";
|
|
116
|
+
ja: "ja";
|
|
117
|
+
"en+ja": "en+ja";
|
|
118
|
+
}>>;
|
|
119
|
+
publishes: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
120
|
+
kind: z.ZodEnum<{
|
|
121
|
+
web: "web";
|
|
122
|
+
npm: "npm";
|
|
123
|
+
}>;
|
|
124
|
+
visibility: z.ZodOptional<z.ZodEnum<{
|
|
125
|
+
public: "public";
|
|
126
|
+
private: "private";
|
|
127
|
+
"future-public": "future-public";
|
|
128
|
+
}>>;
|
|
129
|
+
language: z.ZodOptional<z.ZodEnum<{
|
|
130
|
+
en: "en";
|
|
131
|
+
ja: "ja";
|
|
132
|
+
"en+ja": "en+ja";
|
|
133
|
+
}>>;
|
|
134
|
+
}, z.core.$loose>>>;
|
|
135
|
+
}, z.core.$loose>>>;
|
|
136
|
+
}, z.core.$loose>;
|
|
96
137
|
/** Inferred runtime type for {@link ManifestSchema}. */
|
|
97
138
|
type Manifest = z.infer<typeof ManifestSchema>;
|
|
139
|
+
/**
|
|
140
|
+
* The unrecognized TOP-LEVEL keys a parsed manifest carries — fields preserved by
|
|
141
|
+
* the loose schema that this basou does not know. Returned sorted, for surfacing as
|
|
142
|
+
* an advisory by the read-modify-write commands so preservation is not silent (a
|
|
143
|
+
* newer version's section, or a hand-added/typo'd key, is flagged rather than
|
|
144
|
+
* dropped). Nested unknown keys are preserved too but not enumerated here; this is
|
|
145
|
+
* the high-signal top-level case. Read-only — never mutates.
|
|
146
|
+
*/
|
|
147
|
+
declare function unknownManifestKeys(manifest: Manifest): string[];
|
|
98
148
|
|
|
99
149
|
declare const SessionInnerImportSchema: z.ZodObject<{
|
|
100
150
|
id: z.ZodOptional<z.ZodString & z.ZodType<`ses_${string}`, string, z.core.$ZodTypeInternals<`ses_${string}`, string>>>;
|
|
@@ -434,6 +484,10 @@ declare const SessionImportPayloadSchema: z.ZodObject<{
|
|
|
434
484
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
435
485
|
type: z.ZodLiteral<"note_added">;
|
|
436
486
|
body: z.ZodString;
|
|
487
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
488
|
+
note: "note";
|
|
489
|
+
next_step: "next_step";
|
|
490
|
+
}>>;
|
|
437
491
|
}, z.core.$strip>, z.ZodObject<{
|
|
438
492
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
439
493
|
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
@@ -982,6 +1036,10 @@ declare const NoteAddedEventSchema: z.ZodObject<{
|
|
|
982
1036
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
983
1037
|
type: z.ZodLiteral<"note_added">;
|
|
984
1038
|
body: z.ZodString;
|
|
1039
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
1040
|
+
note: "note";
|
|
1041
|
+
next_step: "next_step";
|
|
1042
|
+
}>>;
|
|
985
1043
|
}, z.core.$strip>;
|
|
986
1044
|
declare const AdapterOutputEventSchema: z.ZodObject<{
|
|
987
1045
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
@@ -1219,6 +1277,10 @@ declare const EventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1219
1277
|
prev_hash: z.ZodOptional<z.ZodString>;
|
|
1220
1278
|
type: z.ZodLiteral<"note_added">;
|
|
1221
1279
|
body: z.ZodString;
|
|
1280
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
1281
|
+
note: "note";
|
|
1282
|
+
next_step: "next_step";
|
|
1283
|
+
}>>;
|
|
1222
1284
|
}, z.core.$strip>, z.ZodObject<{
|
|
1223
1285
|
schema_version: z.ZodLiteral<"0.1.0">;
|
|
1224
1286
|
id: z.ZodString & z.ZodType<`evt_${string}`, string, z.core.$ZodTypeInternals<`evt_${string}`, string>>;
|
|
@@ -2007,6 +2069,22 @@ type DiffResult = {
|
|
|
2007
2069
|
*/
|
|
2008
2070
|
declare function getDiff(repoRoot: string, baseRef: string, headRef: string): Promise<DiffResult>;
|
|
2009
2071
|
|
|
2072
|
+
/**
|
|
2073
|
+
* Build a {@link SimpleGit} instance bound to `repoRoot`. Production callers
|
|
2074
|
+
* use this single helper so any future tightening (additional safety opts,
|
|
2075
|
+
* environment scrubbing, ...) lands in one place. Test fixtures that need
|
|
2076
|
+
* `unsafe.allowUnsafeConfigPaths` for isolated `GIT_CONFIG_*` paths build
|
|
2077
|
+
* their own SimpleGit locally and intentionally bypass this helper.
|
|
2078
|
+
*/
|
|
2079
|
+
declare function safeSimpleGit(repoRoot: string): SimpleGit;
|
|
2080
|
+
/**
|
|
2081
|
+
* Detect "git executable not found" across error wrappers used by simple-git.
|
|
2082
|
+
* simple-git surfaces spawn errors as `GitError` instances which discard the
|
|
2083
|
+
* original errno `code` property — only the underlying `"spawn git ENOENT"`
|
|
2084
|
+
* text survives in the message string. We therefore check both the errno
|
|
2085
|
+
* `code` (via {@link findErrorCode}) and the message chain.
|
|
2086
|
+
*/
|
|
2087
|
+
declare function isGitNotFound(error: unknown): boolean;
|
|
2010
2088
|
/**
|
|
2011
2089
|
* Payload subset of `git_snapshot` event, mechanically derived from the
|
|
2012
2090
|
* zod-inferred event type. The wrapping event-shape fields
|
|
@@ -3093,6 +3171,11 @@ type DecisionRecord = {
|
|
|
3093
3171
|
title: string;
|
|
3094
3172
|
occurredAt: string;
|
|
3095
3173
|
};
|
|
3174
|
+
type NoteRecord = {
|
|
3175
|
+
body: string;
|
|
3176
|
+
sessionId: string;
|
|
3177
|
+
occurredAt: string;
|
|
3178
|
+
};
|
|
3096
3179
|
type PendingApproval = {
|
|
3097
3180
|
id: string;
|
|
3098
3181
|
risk: string;
|
|
@@ -3149,6 +3232,11 @@ type OrientationSummary = {
|
|
|
3149
3232
|
/** Most recent `decision_recorded` across all sessions; null when none. */
|
|
3150
3233
|
latestDecision: DecisionRecord | null;
|
|
3151
3234
|
decisionCount: number;
|
|
3235
|
+
/**
|
|
3236
|
+
* Most recent `note_added` over non-archived sessions — the recorded next
|
|
3237
|
+
* step / handoff ("次の起点") surfaced in the forward section; null when none.
|
|
3238
|
+
*/
|
|
3239
|
+
latestNote: NoteRecord | null;
|
|
3152
3240
|
/** related_files of the latest session, deduped + sorted + capped at the display limit. */
|
|
3153
3241
|
relatedFiles: {
|
|
3154
3242
|
displayed: string[];
|
|
@@ -3165,6 +3253,14 @@ type OrientationSummary = {
|
|
|
3165
3253
|
newestStartedAt: string | null;
|
|
3166
3254
|
/** source.kind of the newest non-archived session, or null when none captured. */
|
|
3167
3255
|
newestSource: string | null;
|
|
3256
|
+
/**
|
|
3257
|
+
* Tail of captured activity over non-archived sessions = max of each
|
|
3258
|
+
* session's boundary (`ended_at` ?? `started_at`) and every captured event's
|
|
3259
|
+
* `occurred_at`. Folding event times covers a live session whose `ended_at`
|
|
3260
|
+
* is not yet written. Used to flag a latest-recorded decision that trails
|
|
3261
|
+
* real activity; null when no non-archived sessions exist.
|
|
3262
|
+
*/
|
|
3263
|
+
latestActivityAt: string | null;
|
|
3168
3264
|
/** Session counts per source kind, sorted by kind. Counts only — never volume/time. */
|
|
3169
3265
|
bySource: SourceCount[];
|
|
3170
3266
|
/** manifest `import.source_roots`, or null when single-root / unreadable. */
|
|
@@ -3200,6 +3296,884 @@ declare function summarizeOrientation(input: OrientationRendererInput): Promise<
|
|
|
3200
3296
|
*/
|
|
3201
3297
|
declare function renderOrientation(input: OrientationRendererInput): Promise<OrientationRendererResult>;
|
|
3202
3298
|
|
|
3299
|
+
/**
|
|
3300
|
+
* Project roster drift (the "saddle" model). A project's repos are DECLARED
|
|
3301
|
+
* once in the manifest's `repos` list; the capture config (`source_roots`) must
|
|
3302
|
+
* cover every declared repo. This computes the drift between the two so
|
|
3303
|
+
* `basou project check` can surface a declared repo that is NOT being captured
|
|
3304
|
+
* — the class of bug where a companion repo was wired into the workspace but
|
|
3305
|
+
* never added to `source_roots`, so its work silently fell out of capture.
|
|
3306
|
+
*
|
|
3307
|
+
* Pure: it compares declared relative paths against captured relative paths and
|
|
3308
|
+
* performs no filesystem or git I/O. Paths are compared as declared (both lists
|
|
3309
|
+
* use the same machine-portable relative-path form), not resolved on disk.
|
|
3310
|
+
*/
|
|
3311
|
+
type RepoVisibility = "public" | "private" | "future-public";
|
|
3312
|
+
/**
|
|
3313
|
+
* The audience-driven language axis. Independent of visibility: a private repo
|
|
3314
|
+
* can publish English content, a public repo can carry bilingual docs. `en` /
|
|
3315
|
+
* `ja` for a single audience, `en+ja` when both are served.
|
|
3316
|
+
*/
|
|
3317
|
+
type RepoLanguage = "en" | "ja" | "en+ja";
|
|
3318
|
+
/** A published surface a repo emits: a deployed website or a package registry. */
|
|
3319
|
+
type PublishKind = "web" | "npm";
|
|
3320
|
+
/**
|
|
3321
|
+
* One published surface. Its visibility and language are INDEPENDENT of the
|
|
3322
|
+
* source repo's: a private repo commonly publishes a public website. Both are
|
|
3323
|
+
* optional so a surface can be declared
|
|
3324
|
+
* before those facts are pinned down (mirroring how `adopt` leaves repo
|
|
3325
|
+
* visibility unset for the operator to fill in).
|
|
3326
|
+
*/
|
|
3327
|
+
type PublishTarget = {
|
|
3328
|
+
kind: PublishKind;
|
|
3329
|
+
visibility?: RepoVisibility | undefined;
|
|
3330
|
+
language?: RepoLanguage | undefined;
|
|
3331
|
+
};
|
|
3332
|
+
type RepoEntry = {
|
|
3333
|
+
/** Path relative to the manifest repo root (e.g. ".", "../takuhon"). */
|
|
3334
|
+
path: string;
|
|
3335
|
+
visibility?: RepoVisibility | undefined;
|
|
3336
|
+
/** Source language (commits/comments/code, read by contributors). Independent of visibility. */
|
|
3337
|
+
language?: RepoLanguage | undefined;
|
|
3338
|
+
/** Published surfaces this repo emits (opt-in; absent for a repo that publishes nothing). */
|
|
3339
|
+
publishes?: PublishTarget[] | undefined;
|
|
3340
|
+
};
|
|
3341
|
+
type RosterDriftSummary = {
|
|
3342
|
+
declaredCount: number;
|
|
3343
|
+
capturedCount: number;
|
|
3344
|
+
/** Declared in `repos` but absent from `source_roots`: a capture gap. */
|
|
3345
|
+
gaps: RepoEntry[];
|
|
3346
|
+
/** In `source_roots` but not declared in `repos` (e.g. a workspace view, or a stray). */
|
|
3347
|
+
extra: string[];
|
|
3348
|
+
/** Declared paths that are also captured. */
|
|
3349
|
+
matched: string[];
|
|
3350
|
+
/** True when there is no capture gap (every declared repo is covered). */
|
|
3351
|
+
ok: boolean;
|
|
3352
|
+
};
|
|
3353
|
+
/**
|
|
3354
|
+
* Compute the {@link RosterDriftSummary} for a project. A declared repo missing
|
|
3355
|
+
* from the captured set is a `gap` (the surfaced suspicion); a captured path not
|
|
3356
|
+
* in the declared set is `extra` (commonly the workspace view, which is a
|
|
3357
|
+
* capture source but not itself a project repo). With no declared roster, there
|
|
3358
|
+
* are no gaps (nothing to check against) and every captured path is `extra`.
|
|
3359
|
+
*/
|
|
3360
|
+
declare function summarizeRosterDrift(input: {
|
|
3361
|
+
repos?: RepoEntry[];
|
|
3362
|
+
sourceRoots?: string[];
|
|
3363
|
+
}): RosterDriftSummary;
|
|
3364
|
+
type SourceRootsReconcile = {
|
|
3365
|
+
/**
|
|
3366
|
+
* The reconciled `source_roots`: the existing entries verbatim, then every
|
|
3367
|
+
* declared repo path that was missing (normalized, in roster order). Existing
|
|
3368
|
+
* order and form are preserved so the manifest diff is minimal and reversible.
|
|
3369
|
+
*/
|
|
3370
|
+
next: string[];
|
|
3371
|
+
/** Declared repo paths (normalized) that were appended because `source_roots` did not cover them. */
|
|
3372
|
+
added: string[];
|
|
3373
|
+
/** True when `source_roots` already covers every declared repo (`next` equals the current list). */
|
|
3374
|
+
unchanged: boolean;
|
|
3375
|
+
};
|
|
3376
|
+
/**
|
|
3377
|
+
* Derive the `source_roots` a project's declared repo roster requires. The
|
|
3378
|
+
* roster (`repos`) is the single source of truth for which repos belong to the
|
|
3379
|
+
* project; this is the actuator behind `basou project sync`, computing the
|
|
3380
|
+
* additive reconciliation so every declared repo is captured.
|
|
3381
|
+
*
|
|
3382
|
+
* ADDITIVE ONLY: it appends declared paths that are missing and never removes
|
|
3383
|
+
* an existing entry. A captured-but-undeclared path (commonly the generated
|
|
3384
|
+
* workspace view — a legitimate capture source that is not itself a project
|
|
3385
|
+
* repo) is preserved; pruning strays is deferred to the slice that generates
|
|
3386
|
+
* the view (so basou knows which extras it owns). Existing entries are kept
|
|
3387
|
+
* byte-identical; only appended paths are normalized.
|
|
3388
|
+
*
|
|
3389
|
+
* Pure: no filesystem or git I/O. Paths are compared in the same normalized
|
|
3390
|
+
* form as {@link summarizeRosterDrift}, so a trailing-slash variant of an
|
|
3391
|
+
* already-captured repo is not re-appended.
|
|
3392
|
+
*/
|
|
3393
|
+
declare function reconcileSourceRoots(input: {
|
|
3394
|
+
repos?: RepoEntry[];
|
|
3395
|
+
sourceRoots?: string[];
|
|
3396
|
+
}): SourceRootsReconcile;
|
|
3397
|
+
/**
|
|
3398
|
+
* On-disk classification of a source-root candidate during adoption: a git repo
|
|
3399
|
+
* root (→ becomes a roster entry), a resolved-but-non-repo directory (the
|
|
3400
|
+
* generated workspace view, `/tmp`, a scratch dir → excluded), or a path that
|
|
3401
|
+
* could not be resolved on disk (→ excluded).
|
|
3402
|
+
*/
|
|
3403
|
+
type AdoptCandidateKind = "repo" | "non-repo" | "unresolved";
|
|
3404
|
+
type AdoptCandidate = {
|
|
3405
|
+
/** Source-root path as declared (relative to the manifest root). */
|
|
3406
|
+
path: string;
|
|
3407
|
+
/** On-disk classification; the filesystem probing that produces it is the caller's job. */
|
|
3408
|
+
kind: AdoptCandidateKind;
|
|
3409
|
+
};
|
|
3410
|
+
type RosterAdoptionPlan = {
|
|
3411
|
+
/** Proposed `repos` entries: the candidates that are git repos (visibility left unset for the operator). */
|
|
3412
|
+
repos: RepoEntry[];
|
|
3413
|
+
/** Candidates excluded from the roster, with why (a non-repo directory, or an unresolvable path). */
|
|
3414
|
+
excluded: {
|
|
3415
|
+
path: string;
|
|
3416
|
+
kind: Exclude<AdoptCandidateKind, "repo">;
|
|
3417
|
+
}[];
|
|
3418
|
+
};
|
|
3419
|
+
/**
|
|
3420
|
+
* Plan a `repos` roster from classified source-root candidates (the actuator
|
|
3421
|
+
* behind `basou project adopt`). Pure: it partitions already-classified
|
|
3422
|
+
* candidates — the realpath / `.git` filesystem probing that produces each
|
|
3423
|
+
* `kind` is the caller's job, so this stays testable without disk I/O.
|
|
3424
|
+
*
|
|
3425
|
+
* A git repo becomes a roster entry (path only; visibility is left unset because
|
|
3426
|
+
* it is a human judgment, kept independent of the other axes). A non-repo
|
|
3427
|
+
* (commonly the generated workspace view) or an unresolvable path is excluded and
|
|
3428
|
+
* reported, so the operator sees what was dropped and why before editing. Repo
|
|
3429
|
+
* paths are deduped by normalized form, preserving the first declared form and
|
|
3430
|
+
* order.
|
|
3431
|
+
*/
|
|
3432
|
+
declare function planRosterAdoption(candidates: AdoptCandidate[]): RosterAdoptionPlan;
|
|
3433
|
+
|
|
3434
|
+
/**
|
|
3435
|
+
* Archive (fold) a repo out of a project's declared roster — the inverse of
|
|
3436
|
+
* `adopt` + `sync`, and the first PRUNING step in the saddle model (every prior
|
|
3437
|
+
* slice was additive and deliberately deferred removal). When a repo has served
|
|
3438
|
+
* its purpose, archiving removes it from the declared `repos` roster and prunes
|
|
3439
|
+
* its capture entry from `source_roots`, so it is no longer part of the project
|
|
3440
|
+
* or scanned by `refresh`.
|
|
3441
|
+
*
|
|
3442
|
+
* Pure: it computes the manifest mutation from the DECLARED lists alone — no
|
|
3443
|
+
* filesystem or git I/O — so it works even when the repo is already gone from
|
|
3444
|
+
* disk (the common "I deleted the repo, now clean basou" case). Historical
|
|
3445
|
+
* captured data in the anchor is NOT touched (archiving stops future capture,
|
|
3446
|
+
* it does not erase the past). The repo-side wiring teardown (view symlink,
|
|
3447
|
+
* instruction symlinks, .gitignore, canonical) is the caller's separate,
|
|
3448
|
+
* higher-blast-radius concern; this only mutates the manifest's declaration.
|
|
3449
|
+
*/
|
|
3450
|
+
|
|
3451
|
+
type ArchivePlan = {
|
|
3452
|
+
/** The normalized target path being archived. */
|
|
3453
|
+
target: string;
|
|
3454
|
+
/** True when the target is declared in the roster. */
|
|
3455
|
+
found: boolean;
|
|
3456
|
+
/**
|
|
3457
|
+
* True when the target resolves to the anchor/host (`.`). Archiving the
|
|
3458
|
+
* project's own root is refused: it is the home of the manifest, not a member
|
|
3459
|
+
* repo to fold. The caller writes nothing in this case.
|
|
3460
|
+
*/
|
|
3461
|
+
isAnchor: boolean;
|
|
3462
|
+
/** The roster entry that would be removed (echoed for the report); set only when found & not anchor. */
|
|
3463
|
+
rosterEntry?: RepoEntry | undefined;
|
|
3464
|
+
/** The roster after removal. An empty array means the project closes (the `repos` key is dropped). */
|
|
3465
|
+
nextRepos: RepoEntry[];
|
|
3466
|
+
/** True when removal leaves the roster empty (the unified-instruction project is fully closed). */
|
|
3467
|
+
reposEmptied: boolean;
|
|
3468
|
+
/** The `source_roots` entry (normalized) that would be pruned; set only when the target was captured. */
|
|
3469
|
+
sourceRootRemoval?: string | undefined;
|
|
3470
|
+
/** The `source_roots` after pruning; set only when a prune actually happens. */
|
|
3471
|
+
nextSourceRoots?: string[] | undefined;
|
|
3472
|
+
/** Declared repos remaining after removal. */
|
|
3473
|
+
remainingCount: number;
|
|
3474
|
+
/** True when exactly one repo remains: the project becomes solo and the workspace view is no longer needed. */
|
|
3475
|
+
becomesSolo: boolean;
|
|
3476
|
+
};
|
|
3477
|
+
/**
|
|
3478
|
+
* Compute the {@link ArchivePlan} for folding `target` out of the project. Pure:
|
|
3479
|
+
* it partitions the declared `repos` and `source_roots` by normalized path.
|
|
3480
|
+
*
|
|
3481
|
+
* - Archiving the anchor (`.`, or a path the caller resolved to the manifest
|
|
3482
|
+
* root) is refused — the plan reports `isAnchor` and removes nothing.
|
|
3483
|
+
* - A target not in the roster yields `found: false` and no change (the caller
|
|
3484
|
+
* reports the declared paths so the operator sees what to type).
|
|
3485
|
+
* - Otherwise EVERY roster entry matching the normalized target is removed (so a
|
|
3486
|
+
* path declared twice does not survive), and the matching `source_roots` entry
|
|
3487
|
+
* (commonly the same path) is pruned. Only the EXACT normalized target is
|
|
3488
|
+
* pruned from `source_roots`; entries for every other path — the host `.`, a
|
|
3489
|
+
* generated workspace-view source root — survive.
|
|
3490
|
+
* - When removal empties the roster, `nextRepos` is `[]` and `reposEmptied` is
|
|
3491
|
+
* true (the caller drops the `repos` key — `repos: []` is not a valid roster).
|
|
3492
|
+
*/
|
|
3493
|
+
declare function planArchive(input: {
|
|
3494
|
+
repos?: RepoEntry[];
|
|
3495
|
+
sourceRoots?: string[];
|
|
3496
|
+
target: string;
|
|
3497
|
+
targetIsAnchor?: boolean;
|
|
3498
|
+
}): ArchivePlan;
|
|
3499
|
+
|
|
3500
|
+
/**
|
|
3501
|
+
* Plan the agent instruction-file `.gitignore` entries a declared repo needs
|
|
3502
|
+
* (the first generation step of the "saddle" model). For a public-facing repo,
|
|
3503
|
+
* the agent instruction files (AGENTS.md, CLAUDE.md, …) must be GITIGNORED so the
|
|
3504
|
+
* gitignored symlinks to the private canonical never enter public git history.
|
|
3505
|
+
* `basou project gitignore` reconciles each repo's `.gitignore` to that; this is
|
|
3506
|
+
* the pure planner behind it.
|
|
3507
|
+
*
|
|
3508
|
+
* Pure: it diffs the REQUIRED patterns against the repo's CURRENT `.gitignore`
|
|
3509
|
+
* lines (both gathered by the caller) and reports only what is MISSING — it never
|
|
3510
|
+
* proposes removing a line. The realpath / file reading / writing is the caller's
|
|
3511
|
+
* job. The privacy decision is visibility-aware: only public / future-public
|
|
3512
|
+
* repos require the patterns (a private anchor may legitimately track its
|
|
3513
|
+
* canonical), and a repo with unset visibility is skipped (reported), never
|
|
3514
|
+
* acted on by guesswork.
|
|
3515
|
+
*/
|
|
3516
|
+
|
|
3517
|
+
/** A declared repo's current `.gitignore` state, gathered by the caller. */
|
|
3518
|
+
type RepoGitignoreFacts = {
|
|
3519
|
+
/** Roster repo path (relative to the manifest root). */
|
|
3520
|
+
path: string;
|
|
3521
|
+
/** Declared visibility; undefined when the operator has not set it yet. */
|
|
3522
|
+
visibility?: RepoVisibility | undefined;
|
|
3523
|
+
/** False when the repo path could not be resolved / is not a usable git repo. */
|
|
3524
|
+
reachable: boolean;
|
|
3525
|
+
/** Existing `.gitignore` lines, trimmed; an empty array when there is no `.gitignore`. */
|
|
3526
|
+
currentLines: string[];
|
|
3527
|
+
};
|
|
3528
|
+
/** The patterns to ADD to one repo's `.gitignore` (never any to remove). */
|
|
3529
|
+
type RepoGitignorePlan = {
|
|
3530
|
+
path: string;
|
|
3531
|
+
toAdd: string[];
|
|
3532
|
+
};
|
|
3533
|
+
type GitignorePlanSummary = {
|
|
3534
|
+
/** Repos that need patterns added (those with an empty `toAdd` are omitted). */
|
|
3535
|
+
plans: RepoGitignorePlan[];
|
|
3536
|
+
/** Repo paths skipped because visibility is unset (cannot decide safely). */
|
|
3537
|
+
unknown: string[];
|
|
3538
|
+
/** Repo paths that could not be resolved / are not usable git repos. */
|
|
3539
|
+
unreachable: string[];
|
|
3540
|
+
/**
|
|
3541
|
+
* True only when nothing needs adding AND every repo was judgeable and
|
|
3542
|
+
* reachable — so a clean verdict is never claimed while some repos were
|
|
3543
|
+
* skipped (unset visibility) or could not be inspected (unreachable).
|
|
3544
|
+
*/
|
|
3545
|
+
ok: boolean;
|
|
3546
|
+
};
|
|
3547
|
+
/**
|
|
3548
|
+
* Compute the {@link GitignorePlanSummary}: for each public-facing, reachable
|
|
3549
|
+
* repo, the `required` patterns that are not already present in its `.gitignore`
|
|
3550
|
+
* (compared by trimmed exact line). Private repos require nothing; unset
|
|
3551
|
+
* visibility is reported as `unknown` and unreachable repos as `unreachable`.
|
|
3552
|
+
* `ok` is true when no repo needs any addition.
|
|
3553
|
+
*/
|
|
3554
|
+
declare function planGitignore(input: {
|
|
3555
|
+
repos: RepoGitignoreFacts[];
|
|
3556
|
+
required: string[];
|
|
3557
|
+
}): GitignorePlanSummary;
|
|
3558
|
+
|
|
3559
|
+
/**
|
|
3560
|
+
* Agent instruction-file "A preset" generation. A repo's
|
|
3561
|
+
* canonical instruction file splits into a STABLE PRESET (its source
|
|
3562
|
+
* visibility, source language, and published surfaces — facts derived from the
|
|
3563
|
+
* manifest) and a HAND-AUTHORED POLICY (tech choices, coding rules). This
|
|
3564
|
+
* renders the stable preset from the declaration so the operator stops
|
|
3565
|
+
* hand-typing it into every prompt, and plans how that generated region
|
|
3566
|
+
* reconciles against each repo's canonical — so `basou project preset` keeps it
|
|
3567
|
+
* in sync without ever touching the hand-authored content around it.
|
|
3568
|
+
*
|
|
3569
|
+
* Pure: it renders deterministic markdown from declared fields and judges
|
|
3570
|
+
* already-gathered facts (does the canonical exist? what does its generated
|
|
3571
|
+
* region currently hold?). The filesystem / marker reading / writing is the
|
|
3572
|
+
* caller's job.
|
|
3573
|
+
*/
|
|
3574
|
+
|
|
3575
|
+
/** The declared fields the preset block is rendered from. */
|
|
3576
|
+
type PresetRepo = {
|
|
3577
|
+
visibility?: RepoVisibility | undefined;
|
|
3578
|
+
language?: RepoLanguage | undefined;
|
|
3579
|
+
publishes?: PublishTarget[] | undefined;
|
|
3580
|
+
};
|
|
3581
|
+
/**
|
|
3582
|
+
* Whether a repo has anything to render. A repo with no visibility, no language,
|
|
3583
|
+
* and no published surface yields an all-"未設定" block that helps no one, so it
|
|
3584
|
+
* is reported as `undeclared` rather than generated.
|
|
3585
|
+
*/
|
|
3586
|
+
declare function isRenderable(repo: PresetRepo): boolean;
|
|
3587
|
+
/**
|
|
3588
|
+
* Render the stable-preset markdown block (the content that lives BETWEEN the
|
|
3589
|
+
* BASOU:GENERATED markers in a canonical). Deterministic and OSS-generic: it
|
|
3590
|
+
* derives entirely from the declared fields, embedding no operator-specific
|
|
3591
|
+
* names, so re-running on an unchanged manifest produces byte-identical output
|
|
3592
|
+
* (the basis for drift detection). The published surfaces are listed in their
|
|
3593
|
+
* declared order. Returns the block WITHOUT a trailing newline; the marker
|
|
3594
|
+
* writer adds the surrounding structure.
|
|
3595
|
+
*/
|
|
3596
|
+
declare function renderPresetBlock(repo: PresetRepo): string;
|
|
3597
|
+
/**
|
|
3598
|
+
* The canonical's marker state as parsed by the caller (mirrors
|
|
3599
|
+
* `markdown-store`'s `MarkerSection.kind`). `ok` means exactly one well-ordered
|
|
3600
|
+
* marker pair; any other value means the generated region cannot be located
|
|
3601
|
+
* safely, so the preset is NOT injected (we never clobber a hand-authored file).
|
|
3602
|
+
*/
|
|
3603
|
+
type PresetMarkerKind = "ok" | "no_markers" | "missing_start" | "missing_end" | "multiple_pairs" | "wrong_order";
|
|
3604
|
+
/** The gathered facts for one declared repo. */
|
|
3605
|
+
type RepoPresetFacts = {
|
|
3606
|
+
/** Roster repo path (relative to the manifest root). */
|
|
3607
|
+
path: string;
|
|
3608
|
+
/** True when this repo IS the project anchor (its own AGENTS.md is hand-maintained; skipped). */
|
|
3609
|
+
isAnchor: boolean;
|
|
3610
|
+
/** False when the repo path could not be resolved / is not a usable git repo. */
|
|
3611
|
+
reachable: boolean;
|
|
3612
|
+
/** Declared fields (the render input). */
|
|
3613
|
+
visibility?: RepoVisibility | undefined;
|
|
3614
|
+
language?: RepoLanguage | undefined;
|
|
3615
|
+
publishes?: PublishTarget[] | undefined;
|
|
3616
|
+
/**
|
|
3617
|
+
* The canonical's repo name (`<name>` in `agents/<name>/AGENTS.md`). Two
|
|
3618
|
+
* DISTINCT repos sharing it would write to one canonical, so it is used to
|
|
3619
|
+
* detect that. Set by the caller for reachable, non-anchor repos.
|
|
3620
|
+
*/
|
|
3621
|
+
canonicalName?: string | undefined;
|
|
3622
|
+
/** Whether the canonical file exists on disk. */
|
|
3623
|
+
canonicalPresent: boolean;
|
|
3624
|
+
/**
|
|
3625
|
+
* Whether the present canonical could be read. Defaults to readable; the
|
|
3626
|
+
* caller sets it `false` when the file exists but a non-ENOENT read failed
|
|
3627
|
+
* (e.g. it is a directory, or permission denied), so one bad canonical
|
|
3628
|
+
* degrades only that repo instead of crashing the whole report.
|
|
3629
|
+
*/
|
|
3630
|
+
canonicalReadable?: boolean | undefined;
|
|
3631
|
+
/** Marker parse result of the canonical (only meaningful when present and readable). */
|
|
3632
|
+
markerKind?: PresetMarkerKind | undefined;
|
|
3633
|
+
/** Current generated-region content (only when `markerKind === "ok"`). */
|
|
3634
|
+
currentBlock?: string | undefined;
|
|
3635
|
+
};
|
|
3636
|
+
/** `create` seeds an absent canonical; `update` replaces the region of an existing one. */
|
|
3637
|
+
type PresetAction = "create" | "update";
|
|
3638
|
+
/** A repo whose canonical's generated region will be created or updated. */
|
|
3639
|
+
type RepoPresetPlan = {
|
|
3640
|
+
path: string;
|
|
3641
|
+
canonicalName: string;
|
|
3642
|
+
action: PresetAction;
|
|
3643
|
+
/** The block that will be written (the marker-delimited content). */
|
|
3644
|
+
desiredBlock: string;
|
|
3645
|
+
};
|
|
3646
|
+
/**
|
|
3647
|
+
* A canonical that exists but whose markers cannot be located safely (absent or
|
|
3648
|
+
* malformed). The region is NOT injected — surfaced so the operator can add the
|
|
3649
|
+
* markers (or remove the malformed ones) by hand.
|
|
3650
|
+
*/
|
|
3651
|
+
type PresetMarkerConflict = {
|
|
3652
|
+
repo: string;
|
|
3653
|
+
reason: Exclude<PresetMarkerKind, "ok">;
|
|
3654
|
+
};
|
|
3655
|
+
/**
|
|
3656
|
+
* Two or more DISTINCT declared repos whose canonical resolves to the same
|
|
3657
|
+
* `agents/<canonicalName>/AGENTS.md`. They would write over one canonical, so
|
|
3658
|
+
* neither is generated; the operator must disambiguate.
|
|
3659
|
+
*/
|
|
3660
|
+
type PresetCollision = {
|
|
3661
|
+
canonicalName: string;
|
|
3662
|
+
repos: string[];
|
|
3663
|
+
};
|
|
3664
|
+
type PresetPlanSummary = {
|
|
3665
|
+
/** Repos whose canonical's generated region will be created/updated (only those with work). */
|
|
3666
|
+
plans: RepoPresetPlan[];
|
|
3667
|
+
/** Repos already in sync (canonical present, ok markers, block matches). */
|
|
3668
|
+
inSync: string[];
|
|
3669
|
+
/** Repos with nothing declared to render (no visibility, language, or published surface). */
|
|
3670
|
+
undeclared: string[];
|
|
3671
|
+
/** Canonicals that exist but whose markers are absent/malformed — not overwritten. */
|
|
3672
|
+
markerConflicts: PresetMarkerConflict[];
|
|
3673
|
+
/** Repos whose canonical exists but could not be read (degraded, not generated). */
|
|
3674
|
+
unreadable: string[];
|
|
3675
|
+
/** Groups of distinct repos that resolve to the same canonical (ambiguous; not generated). */
|
|
3676
|
+
collisions: PresetCollision[];
|
|
3677
|
+
/** Repos that resolve to the anchor (their own AGENTS.md is hand-maintained; skipped). */
|
|
3678
|
+
anchors: string[];
|
|
3679
|
+
/** Repo paths that could not be resolved / are not usable git repos. */
|
|
3680
|
+
unreachable: string[];
|
|
3681
|
+
/**
|
|
3682
|
+
* True only when nothing needs writing AND there are no marker conflicts, no
|
|
3683
|
+
* unreadable canonicals, no collisions, no unreachable repos, and no
|
|
3684
|
+
* undeclared repos — so a clean "all in sync" verdict is never claimed while
|
|
3685
|
+
* some repo was skipped or unjudgeable. Anchors do not block it (they are
|
|
3686
|
+
* intentionally not generated).
|
|
3687
|
+
*/
|
|
3688
|
+
ok: boolean;
|
|
3689
|
+
};
|
|
3690
|
+
/**
|
|
3691
|
+
* Compute the {@link PresetPlanSummary} from per-repo facts. For each declared,
|
|
3692
|
+
* non-anchor, reachable, renderable repo: an absent canonical is a `create`, an
|
|
3693
|
+
* existing canonical with an `ok` marker region is an `update` (or `inSync` when
|
|
3694
|
+
* the region already matches), and a canonical with absent/malformed markers is
|
|
3695
|
+
* a {@link PresetMarkerConflict} (never overwritten). The anchor is skipped
|
|
3696
|
+
* (`anchors`), an unrenderable repo is `undeclared`, and an unresolvable repo is
|
|
3697
|
+
* `unreachable`.
|
|
3698
|
+
*
|
|
3699
|
+
* Robustness:
|
|
3700
|
+
* - Facts are deduped by normalized path (first wins), so a repo listed twice
|
|
3701
|
+
* never yields duplicate plans / report entries.
|
|
3702
|
+
* - Two DISTINCT repos resolving to the same canonical name are a
|
|
3703
|
+
* {@link PresetCollision} and neither is generated (silent clobbering of one
|
|
3704
|
+
* canonical is surfaced, not actioned).
|
|
3705
|
+
*/
|
|
3706
|
+
declare function summarizePresetPlan(facts: RepoPresetFacts[]): PresetPlanSummary;
|
|
3707
|
+
|
|
3708
|
+
/**
|
|
3709
|
+
* Rename (re-path) a repo in a project's declared roster. When a repo's
|
|
3710
|
+
* directory is moved or renamed on disk, its declared `path` (and the matching
|
|
3711
|
+
* `source_roots` capture entry) must follow, or the roster drifts from reality.
|
|
3712
|
+
* This is the saddle model's maintenance counterpart to `archive` — it mutates
|
|
3713
|
+
* the manifest's path references rather than removing them.
|
|
3714
|
+
*
|
|
3715
|
+
* Pure: it computes the mutation from the DECLARED lists alone — no filesystem
|
|
3716
|
+
* or git I/O — so it works regardless of whether the move has happened on disk
|
|
3717
|
+
* yet. The repo-side wiring that embeds the old basename (the anchor canonical
|
|
3718
|
+
* `agents/<basename>/AGENTS.md`, the workspace-view symlink, the relative
|
|
3719
|
+
* targets of the repo's own instruction symlinks) is the caller's separate
|
|
3720
|
+
* concern; this only re-paths the declaration.
|
|
3721
|
+
*/
|
|
3722
|
+
|
|
3723
|
+
type RenamePlan = {
|
|
3724
|
+
/** The normalized source path being renamed. */
|
|
3725
|
+
oldTarget: string;
|
|
3726
|
+
/** The normalized destination path. */
|
|
3727
|
+
newTarget: string;
|
|
3728
|
+
/** True when old and new normalize to the same path (nothing to do). */
|
|
3729
|
+
noop: boolean;
|
|
3730
|
+
/**
|
|
3731
|
+
* True when the source resolves to the anchor/host (`.`). Renaming the
|
|
3732
|
+
* project's own root is refused; the caller writes nothing.
|
|
3733
|
+
*/
|
|
3734
|
+
isAnchor: boolean;
|
|
3735
|
+
/** True when the source path is declared in the roster. */
|
|
3736
|
+
found: boolean;
|
|
3737
|
+
/** True when the destination path is ALREADY declared (a distinct entry) — refused to avoid a duplicate. */
|
|
3738
|
+
collision: boolean;
|
|
3739
|
+
/** The roster entry being renamed (echoed for the report); set only in the actionable case. */
|
|
3740
|
+
rosterEntry?: RepoEntry | undefined;
|
|
3741
|
+
/** The roster after re-pathing the entry (other fields preserved). */
|
|
3742
|
+
nextRepos: RepoEntry[];
|
|
3743
|
+
/** True when the roster changed. */
|
|
3744
|
+
reposChanged: boolean;
|
|
3745
|
+
/** The old normalized path that was re-pathed in `source_roots`; set only when it was captured. */
|
|
3746
|
+
sourceRootRenamed?: string | undefined;
|
|
3747
|
+
/** The `source_roots` after re-pathing; set only when a rename happened. */
|
|
3748
|
+
nextSourceRoots?: string[] | undefined;
|
|
3749
|
+
/** True when the basename changes (old/new last segment differ) — the repo-side canonical/view names need renaming too. */
|
|
3750
|
+
basenameChanged: boolean;
|
|
3751
|
+
};
|
|
3752
|
+
/** The last path segment of a normalized relative path (e.g. "../a/x" => "x", "." => "."). */
|
|
3753
|
+
declare function pathBasename(p: string): string;
|
|
3754
|
+
/**
|
|
3755
|
+
* Compute the {@link RenamePlan} for re-pathing `oldPath` to `newPath`. Pure: it
|
|
3756
|
+
* re-maps the declared `repos` and `source_roots` by normalized path.
|
|
3757
|
+
*
|
|
3758
|
+
* - A no-op (old === new), renaming the anchor, a source not in the roster, or a
|
|
3759
|
+
* destination that already exists as a distinct entry (collision) all change
|
|
3760
|
+
* nothing — the caller writes nothing and the report explains.
|
|
3761
|
+
* - Otherwise EVERY roster entry matching the old path is re-pathed to the new
|
|
3762
|
+
* path (preserving its visibility/language/publishes), and the result is
|
|
3763
|
+
* deduped (so an old path declared twice collapses to one new entry). The
|
|
3764
|
+
* matching `source_roots` entry (commonly the same path) is re-pathed and
|
|
3765
|
+
* deduped likewise; all other entries (the host `.`, a view source root) keep
|
|
3766
|
+
* their position and form.
|
|
3767
|
+
*/
|
|
3768
|
+
declare function planRename(input: {
|
|
3769
|
+
repos?: RepoEntry[];
|
|
3770
|
+
sourceRoots?: string[];
|
|
3771
|
+
oldPath: string;
|
|
3772
|
+
newPath: string;
|
|
3773
|
+
oldIsAnchor?: boolean;
|
|
3774
|
+
}): RenamePlan;
|
|
3775
|
+
|
|
3776
|
+
/**
|
|
3777
|
+
* Plan the agent instruction-file symlinks a declared repo needs (the
|
|
3778
|
+
* generation step that follows `basou project gitignore` in the "saddle"
|
|
3779
|
+
* model). Each repo's agent instruction files are GITIGNORED symlinks that
|
|
3780
|
+
* resolve to a single canonical source kept in the project's private anchor —
|
|
3781
|
+
* so the canonical (which may carry private planning content) is edited once
|
|
3782
|
+
* and every CLI reads it through a symlink, never committed to a public repo's
|
|
3783
|
+
* history.
|
|
3784
|
+
*
|
|
3785
|
+
* The on-disk topology (verified against the operator's live environment) is a
|
|
3786
|
+
* hub-and-spoke:
|
|
3787
|
+
*
|
|
3788
|
+
* <repo>/AGENTS.md -> <anchor>/agents/<repo>/AGENTS.md (the hub → canonical)
|
|
3789
|
+
* <repo>/CLAUDE.md -> AGENTS.md (a spoke → the hub)
|
|
3790
|
+
* <repo>/.github/copilot-instructions.md -> ../AGENTS.md (a spoke → the hub)
|
|
3791
|
+
*
|
|
3792
|
+
* Only AGENTS.md points at the anchor's canonical; CLAUDE.md and Copilot point
|
|
3793
|
+
* back at the repo's own AGENTS.md, so there is exactly one link per repo that
|
|
3794
|
+
* depends on the anchor path. GEMINI.md is intentionally not generated (the
|
|
3795
|
+
* Gemini CLI was discontinued for personal use).
|
|
3796
|
+
*
|
|
3797
|
+
* Pure: it judges already-gathered, per-file facts (does the link exist? does
|
|
3798
|
+
* it point where it should?) and reports only what is MISSING. It never
|
|
3799
|
+
* proposes overwriting an existing file or repointing a link that points
|
|
3800
|
+
* elsewhere — those surface as conflicts for the operator to resolve by hand
|
|
3801
|
+
* (non-destructive, like the additive `.gitignore` planner). The realpath /
|
|
3802
|
+
* symlink reading / writing is the caller's job.
|
|
3803
|
+
*/
|
|
3804
|
+
/**
|
|
3805
|
+
* The on-disk state of one instruction file relative to the symlink it should
|
|
3806
|
+
* be. `correct` = the expected link already exists (idempotent skip); `missing`
|
|
3807
|
+
* = nothing there (ENOENT), so it can be created; `mismatch` = a symlink
|
|
3808
|
+
* pointing somewhere else; `occupied` = a real file or directory; `blocked` =
|
|
3809
|
+
* the path could not be inspected (e.g. a parent component is a file → ENOTDIR,
|
|
3810
|
+
* or permission denied). Only `missing` is actionable; the rest are left
|
|
3811
|
+
* untouched. `blocked` is distinct from `missing` so a non-ENOENT lstat error
|
|
3812
|
+
* is never mistaken for a creatable gap (which would crash `--apply`).
|
|
3813
|
+
*/
|
|
3814
|
+
type InstructionSymlinkState = "correct" | "missing" | "mismatch" | "occupied" | "blocked";
|
|
3815
|
+
/** The gathered facts for one instruction file in one declared repo. */
|
|
3816
|
+
type InstructionSymlinkFact = {
|
|
3817
|
+
/** Repo-relative file name, e.g. "AGENTS.md", ".github/copilot-instructions.md". */
|
|
3818
|
+
name: string;
|
|
3819
|
+
/** The relative symlink target this file should have (computed by the caller). */
|
|
3820
|
+
expectedTarget: string;
|
|
3821
|
+
/** On-disk state of the path. */
|
|
3822
|
+
state: InstructionSymlinkState;
|
|
3823
|
+
/** The link's current target, present only when `state` is `mismatch`. */
|
|
3824
|
+
actualTarget?: string;
|
|
3825
|
+
};
|
|
3826
|
+
/** The gathered symlink facts for one declared repo. */
|
|
3827
|
+
type RepoSymlinkFacts = {
|
|
3828
|
+
/** Roster repo path (relative to the manifest root). */
|
|
3829
|
+
path: string;
|
|
3830
|
+
/**
|
|
3831
|
+
* True when this repo IS the project anchor (it owns the canonical sources, so
|
|
3832
|
+
* it never links to itself). An anchor entry is skipped entirely.
|
|
3833
|
+
*/
|
|
3834
|
+
isAnchor: boolean;
|
|
3835
|
+
/** False when the repo path could not be resolved / is not a usable git repo. */
|
|
3836
|
+
reachable: boolean;
|
|
3837
|
+
/**
|
|
3838
|
+
* Whether the anchor's canonical source for this repo
|
|
3839
|
+
* (`<anchor>/agents/<repo>/AGENTS.md`) exists. Without it the hub link would
|
|
3840
|
+
* dangle, so no links are planned (reported as a missing canonical instead).
|
|
3841
|
+
*/
|
|
3842
|
+
canonicalPresent: boolean;
|
|
3843
|
+
/**
|
|
3844
|
+
* The canonical's repo name (the `<repo>` in `agents/<repo>/AGENTS.md`) this
|
|
3845
|
+
* repo wires to. Two DISTINCT repos sharing one canonical name would silently
|
|
3846
|
+
* collide on a single canonical, so it is used to detect that. Set by the
|
|
3847
|
+
* caller for reachable, canonical-present repos; undefined otherwise.
|
|
3848
|
+
*/
|
|
3849
|
+
canonicalName?: string;
|
|
3850
|
+
/** Per instruction-file facts (empty when anchor / unreachable / canonical absent). */
|
|
3851
|
+
files: InstructionSymlinkFact[];
|
|
3852
|
+
};
|
|
3853
|
+
/** The instruction-file symlinks to CREATE in one repo (only the `missing` ones). */
|
|
3854
|
+
type RepoSymlinkPlan = {
|
|
3855
|
+
path: string;
|
|
3856
|
+
toCreate: {
|
|
3857
|
+
name: string;
|
|
3858
|
+
target: string;
|
|
3859
|
+
}[];
|
|
3860
|
+
};
|
|
3861
|
+
/**
|
|
3862
|
+
* A symlink that already exists but is not what we would generate: a symlink
|
|
3863
|
+
* pointing elsewhere (`mismatch`), a real file/directory (`occupied`), or a path
|
|
3864
|
+
* that could not be inspected (`blocked`, e.g. a parent is a file). Surfaced,
|
|
3865
|
+
* never overwritten.
|
|
3866
|
+
*/
|
|
3867
|
+
type SymlinkConflict = {
|
|
3868
|
+
repo: string;
|
|
3869
|
+
file: string;
|
|
3870
|
+
reason: "mismatch" | "occupied" | "blocked";
|
|
3871
|
+
/** The conflicting link's current target, present only when `reason` is `mismatch`. */
|
|
3872
|
+
actualTarget?: string;
|
|
3873
|
+
};
|
|
3874
|
+
/**
|
|
3875
|
+
* Two or more DISTINCT declared repos whose canonical resolves to the same
|
|
3876
|
+
* `agents/<canonicalName>/AGENTS.md`. They would silently share one canonical,
|
|
3877
|
+
* so neither is auto-wired; the operator must disambiguate.
|
|
3878
|
+
*/
|
|
3879
|
+
type SymlinkCollision = {
|
|
3880
|
+
canonicalName: string;
|
|
3881
|
+
repos: string[];
|
|
3882
|
+
};
|
|
3883
|
+
type SymlinkPlanSummary = {
|
|
3884
|
+
/** Repos with at least one link to create (those with nothing to create are omitted). */
|
|
3885
|
+
plans: RepoSymlinkPlan[];
|
|
3886
|
+
/** Existing files/links that block generation and are left untouched for the operator. */
|
|
3887
|
+
conflicts: SymlinkConflict[];
|
|
3888
|
+
/** Repo paths whose anchor canonical (`agents/<repo>/AGENTS.md`) is absent, so nothing can be wired. */
|
|
3889
|
+
missingCanonical: string[];
|
|
3890
|
+
/** Repo paths that could not be resolved / are not usable git repos. */
|
|
3891
|
+
unreachable: string[];
|
|
3892
|
+
/** Groups of distinct repos that resolve to the same canonical (ambiguous; not auto-wired). */
|
|
3893
|
+
collisions: SymlinkCollision[];
|
|
3894
|
+
/**
|
|
3895
|
+
* True only when nothing needs creating AND there are no conflicts, no missing
|
|
3896
|
+
* canonicals, no unreachable repos, and no collisions — so a clean "all wired"
|
|
3897
|
+
* verdict is never claimed while some repo was blocked, ambiguous, or could not
|
|
3898
|
+
* be inspected.
|
|
3899
|
+
*/
|
|
3900
|
+
ok: boolean;
|
|
3901
|
+
};
|
|
3902
|
+
/**
|
|
3903
|
+
* Compute the {@link SymlinkPlanSummary} from per-repo facts. For each declared,
|
|
3904
|
+
* non-anchor, reachable repo whose canonical exists: a `missing` link becomes a
|
|
3905
|
+
* create, a `mismatch`/`occupied`/`blocked` link becomes a {@link SymlinkConflict}
|
|
3906
|
+
* (never a create — we do not overwrite), and a `correct` link is a no-op. The
|
|
3907
|
+
* anchor is skipped (it owns the canonical), an absent canonical is reported as
|
|
3908
|
+
* `missingCanonical` (no links planned, since the hub would dangle), and an
|
|
3909
|
+
* unresolvable repo as `unreachable`.
|
|
3910
|
+
*
|
|
3911
|
+
* Robustness:
|
|
3912
|
+
* - Facts are deduped by normalized path (first wins), so a repo listed twice in
|
|
3913
|
+
* the manifest never yields duplicate plans (which would make `--apply` create
|
|
3914
|
+
* the same link twice → EEXIST) or duplicate report entries.
|
|
3915
|
+
* - Two DISTINCT repos resolving to the same canonical name are reported as a
|
|
3916
|
+
* {@link SymlinkCollision} and neither is auto-wired (silent sharing of one
|
|
3917
|
+
* canonical is surfaced, not actioned).
|
|
3918
|
+
*
|
|
3919
|
+
* `ok` is true only when there is genuinely nothing to do and every repo was
|
|
3920
|
+
* judgeable, reachable, and unambiguous.
|
|
3921
|
+
*/
|
|
3922
|
+
declare function summarizeSymlinkPlan(facts: RepoSymlinkFacts[]): SymlinkPlanSummary;
|
|
3923
|
+
|
|
3924
|
+
/**
|
|
3925
|
+
* Agent instruction-file wiring (the read-only first step of the "saddle"
|
|
3926
|
+
* model's view/instruction/gitignore generation). For each declared repo, the
|
|
3927
|
+
* agent instruction files (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md)
|
|
3928
|
+
* should be present as GITIGNORED symlinks to a canonical source, never tracked
|
|
3929
|
+
* in a public repo's git history (where they would expose the private canonical
|
|
3930
|
+
* content they point at). This summarizes the on-disk + git facts the CLI
|
|
3931
|
+
* gathers and surfaces the privacy-relevant drift; it generates nothing.
|
|
3932
|
+
*
|
|
3933
|
+
* Pure: it judges already-gathered facts (presence + git-tracked status). The
|
|
3934
|
+
* filesystem / git probing that produces those facts is the caller's job.
|
|
3935
|
+
*/
|
|
3936
|
+
|
|
3937
|
+
/** On-disk + git state of one instruction file in one repo. */
|
|
3938
|
+
type InstructionFileFact = {
|
|
3939
|
+
/** Repo-relative file name, e.g. "AGENTS.md", ".github/copilot-instructions.md". */
|
|
3940
|
+
name: string;
|
|
3941
|
+
/** Exists on disk (a regular file or a symlink, including a broken one). */
|
|
3942
|
+
present: boolean;
|
|
3943
|
+
/** Tracked by the repo's git (committed / staged). */
|
|
3944
|
+
tracked: boolean;
|
|
3945
|
+
};
|
|
3946
|
+
/** The gathered wiring facts for one declared repo. */
|
|
3947
|
+
type RepoWiringFacts = {
|
|
3948
|
+
/** Roster repo path (relative to the manifest root). */
|
|
3949
|
+
path: string;
|
|
3950
|
+
/** Declared visibility; undefined when the operator has not set it yet. */
|
|
3951
|
+
visibility?: RepoVisibility | undefined;
|
|
3952
|
+
/** False when the repo path could not be resolved / is not a usable git repo. */
|
|
3953
|
+
reachable: boolean;
|
|
3954
|
+
/** Per instruction-file facts (omitted/empty when unreachable). */
|
|
3955
|
+
instructionFiles: InstructionFileFact[];
|
|
3956
|
+
};
|
|
3957
|
+
/**
|
|
3958
|
+
* A privacy risk: a public-facing repo tracks an instruction file in git, which
|
|
3959
|
+
* can expose the private canonical content the file points at.
|
|
3960
|
+
*/
|
|
3961
|
+
type WiringRisk = {
|
|
3962
|
+
repo: string;
|
|
3963
|
+
visibility: Extract<RepoVisibility, "public" | "future-public">;
|
|
3964
|
+
file: string;
|
|
3965
|
+
};
|
|
3966
|
+
type WiringSummary = {
|
|
3967
|
+
/** Echo of the per-repo facts (for `--json` and the detailed view). */
|
|
3968
|
+
repos: RepoWiringFacts[];
|
|
3969
|
+
/** Public / future-public repos with a tracked instruction file. */
|
|
3970
|
+
risks: WiringRisk[];
|
|
3971
|
+
/** Repo paths whose visibility is unset, so the privacy verdict cannot be judged. */
|
|
3972
|
+
unknown: string[];
|
|
3973
|
+
/** Repos missing one or more instruction files (a wiring gap a later generate slice fills). */
|
|
3974
|
+
incomplete: {
|
|
3975
|
+
repo: string;
|
|
3976
|
+
missing: string[];
|
|
3977
|
+
}[];
|
|
3978
|
+
/** Repo paths that could not be resolved / are not usable git repos. */
|
|
3979
|
+
unreachable: string[];
|
|
3980
|
+
/** True when there are no risks, no unknown visibility, and no unreachable repos. */
|
|
3981
|
+
ok: boolean;
|
|
3982
|
+
};
|
|
3983
|
+
/**
|
|
3984
|
+
* Summarize {@link RepoWiringFacts} into the privacy-relevant verdict. A
|
|
3985
|
+
* public-facing repo that TRACKS an instruction file is a {@link WiringRisk}
|
|
3986
|
+
* (its git history can expose the private canonical it points at); a repo with
|
|
3987
|
+
* unset visibility cannot be judged (`unknown`); a repo missing instruction
|
|
3988
|
+
* files is `incomplete` (a wiring gap, not a privacy problem). `ok` is true only
|
|
3989
|
+
* when nothing is at risk, every repo is judgeable, and every repo is reachable.
|
|
3990
|
+
*/
|
|
3991
|
+
declare function summarizeWiring(facts: RepoWiringFacts[]): WiringSummary;
|
|
3992
|
+
|
|
3993
|
+
/**
|
|
3994
|
+
* Plan the symlinks a project's throwaway "view" needs (the generation step
|
|
3995
|
+
* after the instruction-file symlinks in the "saddle" model). When a project
|
|
3996
|
+
* has 2+ repos, basou generates a view directory that aggregates every roster
|
|
3997
|
+
* repo via one symlink each, named by the repo's basename and pointing at the
|
|
3998
|
+
* repo (relative to the view):
|
|
3999
|
+
*
|
|
4000
|
+
* <view>/app -> ../app
|
|
4001
|
+
* <view>/anchor -> ../anchor (the anchor's "." entry is aggregated here too,
|
|
4002
|
+
* <view>/app-site -> ../app-site unlike the instruction symlinks)
|
|
4003
|
+
*
|
|
4004
|
+
* The view is git-unmanaged and regenerable; its location is declared once
|
|
4005
|
+
* (`workspace.view` in the manifest) and its contents are derived from the
|
|
4006
|
+
* roster. This is the pure planner: it judges already-gathered, per-repo facts
|
|
4007
|
+
* (does the view link exist? does it point at the repo?) and reports only what
|
|
4008
|
+
* is MISSING. It never overwrites an existing file or repoints a link that
|
|
4009
|
+
* points elsewhere — those surface as conflicts for the operator to resolve by
|
|
4010
|
+
* hand (non-destructive). The realpath / readlink / symlink I/O is the caller's
|
|
4011
|
+
* job.
|
|
4012
|
+
*
|
|
4013
|
+
* Pruning stray entries already in the view IS now in scope (see `toPrune` /
|
|
4014
|
+
* `strayUnknown` and {@link ExistingViewLink}): the ownership model that tells an
|
|
4015
|
+
* orphaned repo link from the view's own instruction files / local state lives
|
|
4016
|
+
* here. Still NOT in scope: generating the view's own instruction files.
|
|
4017
|
+
*/
|
|
4018
|
+
/**
|
|
4019
|
+
* The on-disk state of one repo's view symlink. `correct` = the expected link
|
|
4020
|
+
* exists (idempotent skip); `missing` = nothing there (ENOENT) so it can be
|
|
4021
|
+
* created; `mismatch` = a symlink pointing elsewhere; `occupied` = a real file
|
|
4022
|
+
* or directory; `blocked` = the path could not be inspected (a non-ENOENT lstat
|
|
4023
|
+
* error, e.g. a parent component is a file). Only `missing` is actionable.
|
|
4024
|
+
*/
|
|
4025
|
+
type ViewLinkState = "correct" | "missing" | "mismatch" | "occupied" | "blocked";
|
|
4026
|
+
/** The gathered facts for one roster repo's place in the view. */
|
|
4027
|
+
type ViewRepoFact = {
|
|
4028
|
+
/** Roster repo path (relative to the manifest root), e.g. ".", "../basou". */
|
|
4029
|
+
path: string;
|
|
4030
|
+
/**
|
|
4031
|
+
* False when the repo cannot be aggregated into the view: its path did not
|
|
4032
|
+
* resolve on disk, or it resolves to the view directory itself (a self-link).
|
|
4033
|
+
*/
|
|
4034
|
+
reachable: boolean;
|
|
4035
|
+
/** The view symlink name (the repo's basename), e.g. "basou". Set when reachable. */
|
|
4036
|
+
linkName?: string;
|
|
4037
|
+
/** The relative target the view link should have, e.g. "../basou". Set when reachable. */
|
|
4038
|
+
expectedTarget?: string;
|
|
4039
|
+
/** On-disk state of `<view>/<linkName>`. Set when reachable. */
|
|
4040
|
+
state?: ViewLinkState;
|
|
4041
|
+
/** The link's current target, present only when `state` is `mismatch`. */
|
|
4042
|
+
actualTarget?: string;
|
|
4043
|
+
};
|
|
4044
|
+
/**
|
|
4045
|
+
* An existing view entry that is not what we would generate: a symlink pointing
|
|
4046
|
+
* elsewhere (`mismatch`), a real file/directory (`occupied`), or an
|
|
4047
|
+
* uninspectable path (`blocked`). Surfaced, never overwritten.
|
|
4048
|
+
*/
|
|
4049
|
+
type ViewConflict = {
|
|
4050
|
+
name: string;
|
|
4051
|
+
reason: "mismatch" | "occupied" | "blocked";
|
|
4052
|
+
/** The conflicting link's current target, present only when `reason` is `mismatch`. */
|
|
4053
|
+
actualTarget?: string;
|
|
4054
|
+
};
|
|
4055
|
+
/**
|
|
4056
|
+
* Two or more DISTINCT roster repos whose basename is the same, so they would
|
|
4057
|
+
* collide on a single `<view>/<basename>` link. Neither is auto-wired; the
|
|
4058
|
+
* operator must disambiguate.
|
|
4059
|
+
*/
|
|
4060
|
+
type ViewCollision = {
|
|
4061
|
+
linkName: string;
|
|
4062
|
+
repos: string[];
|
|
4063
|
+
};
|
|
4064
|
+
/**
|
|
4065
|
+
* An entry actually present in the view directory, pre-classified by the caller's
|
|
4066
|
+
* filesystem probe, for stray detection (the inverse of generation). Only
|
|
4067
|
+
* symlinks are candidates — a real file or directory is never the caller's to
|
|
4068
|
+
* remove and is not gathered here. `kind` tells the planner whether a symlink not
|
|
4069
|
+
* tied to any roster repo is a basou-generated repo link safe to prune:
|
|
4070
|
+
*
|
|
4071
|
+
* - `repo`: a relative target that follows to an existing git repository — a
|
|
4072
|
+
* directory containing a `.git` entry, whether a directory OR a gitdir-pointer
|
|
4073
|
+
* FILE (git worktrees and submodules), matching the project family's repo test
|
|
4074
|
+
* (`existsSync(<dir>/.git)`, as `adopt`/`wiring` use). Exactly what
|
|
4075
|
+
* {@link planWorkspaceView}'s `toCreate` produces. A stray of this kind is prunable.
|
|
4076
|
+
* - `broken`: a relative target that does not resolve on disk (e.g. the repo was
|
|
4077
|
+
* moved/deleted). Reported, never auto-pruned (we cannot confirm it was ours).
|
|
4078
|
+
* - `non-repo`: a relative target that resolves to a non-repository path (a file,
|
|
4079
|
+
* or a directory without `.git`). Reported, never auto-pruned.
|
|
4080
|
+
* - `absolute`: an absolute target. basou never writes absolute view links, so it
|
|
4081
|
+
* is not ours. Reported, never auto-pruned.
|
|
4082
|
+
*
|
|
4083
|
+
* The caller filters out the view's OWN instruction-file symlinks (e.g. a top-level
|
|
4084
|
+
* `AGENTS.md`/`CLAUDE.md`) before classifying, so they never surface as strays.
|
|
4085
|
+
*/
|
|
4086
|
+
type ExistingViewLink = {
|
|
4087
|
+
name: string;
|
|
4088
|
+
target: string;
|
|
4089
|
+
kind: "repo" | "broken" | "non-repo" | "absolute";
|
|
4090
|
+
};
|
|
4091
|
+
/**
|
|
4092
|
+
* A view symlink not tied to any current roster repo that was NOT auto-pruned
|
|
4093
|
+
* because we could not confirm it is a basou-generated repo link. Surfaced for
|
|
4094
|
+
* the operator to resolve by hand; never removed.
|
|
4095
|
+
*/
|
|
4096
|
+
type ViewStrayUnknown = {
|
|
4097
|
+
name: string;
|
|
4098
|
+
target: string;
|
|
4099
|
+
reason: "broken" | "non-repo" | "absolute";
|
|
4100
|
+
};
|
|
4101
|
+
type WorkspaceViewPlan = {
|
|
4102
|
+
/** The view symlinks to create (the `missing` ones), as name + relative target. */
|
|
4103
|
+
toCreate: {
|
|
4104
|
+
name: string;
|
|
4105
|
+
target: string;
|
|
4106
|
+
}[];
|
|
4107
|
+
/** Existing entries that block generation and are left untouched. */
|
|
4108
|
+
conflicts: ViewConflict[];
|
|
4109
|
+
/** Distinct repos colliding on one view link name (not auto-wired). */
|
|
4110
|
+
collisions: ViewCollision[];
|
|
4111
|
+
/** Roster repo paths that cannot be aggregated: unresolved on disk, or resolving to the view itself. */
|
|
4112
|
+
unreachable: string[];
|
|
4113
|
+
/**
|
|
4114
|
+
* Stray basou-generated repo links to remove: a view symlink whose name is not
|
|
4115
|
+
* a current roster repo and whose relative target follows to a git repository.
|
|
4116
|
+
* Removed only under `--prune` (its own opt-in, separate from `--apply`).
|
|
4117
|
+
*/
|
|
4118
|
+
toPrune: {
|
|
4119
|
+
name: string;
|
|
4120
|
+
target: string;
|
|
4121
|
+
}[];
|
|
4122
|
+
/**
|
|
4123
|
+
* Stray view symlinks not tied to any roster repo that we did NOT recognize as
|
|
4124
|
+
* a basou-generated repo link (broken, non-repo, or absolute target). Reported,
|
|
4125
|
+
* never auto-pruned.
|
|
4126
|
+
*/
|
|
4127
|
+
strayUnknown: ViewStrayUnknown[];
|
|
4128
|
+
/** Count of repos whose view link is already correct (for the report). */
|
|
4129
|
+
correctCount: number;
|
|
4130
|
+
/**
|
|
4131
|
+
* True only when nothing needs creating, there are no conflicts, no collisions,
|
|
4132
|
+
* no unreachable repos, AND no strays (prunable or unknown) — so a clean "view
|
|
4133
|
+
* in sync" verdict is never claimed while a repo was blocked, ambiguous, could
|
|
4134
|
+
* not be resolved, or the view still carries an entry the roster no longer backs.
|
|
4135
|
+
*/
|
|
4136
|
+
ok: boolean;
|
|
4137
|
+
};
|
|
4138
|
+
/**
|
|
4139
|
+
* Compute the {@link WorkspaceViewPlan} from per-repo facts. For each declared,
|
|
4140
|
+
* reachable, non-colliding repo: a `missing` view link becomes a create, a
|
|
4141
|
+
* `mismatch`/`occupied`/`blocked` link becomes a {@link ViewConflict} (never a
|
|
4142
|
+
* create — we do not overwrite), and a `correct` link is counted. The view
|
|
4143
|
+
* aggregates exactly the DECLARED roster — the anchor is aggregated when present
|
|
4144
|
+
* as its `.` entry (which `adopt` always adds) and, unlike the instruction
|
|
4145
|
+
* symlinks, is NOT skipped; it is not implicitly injected when absent (the roster
|
|
4146
|
+
* is the single source of truth). An unresolvable repo is `unreachable`, and two
|
|
4147
|
+
* distinct repos sharing a basename are a {@link ViewCollision} (neither
|
|
4148
|
+
* auto-wired).
|
|
4149
|
+
*
|
|
4150
|
+
* Stray detection (the inverse of generation): given the entries actually present
|
|
4151
|
+
* in the view (`existing`), any symlink whose name is NOT owned by a declared
|
|
4152
|
+
* roster repo is a stray. A stray classified `repo` (a relative target following to
|
|
4153
|
+
* a git repository — basou's own generation shape) goes to `toPrune` (removed only
|
|
4154
|
+
* under the separate `--prune` opt-in); a stray we cannot confirm is ours (broken,
|
|
4155
|
+
* non-repo, or absolute target) goes to `strayUnknown` (reported, never removed).
|
|
4156
|
+
*
|
|
4157
|
+
* Ownership (what is NEVER a stray): a name is owned if it is the link name of a
|
|
4158
|
+
* reachable roster repo OR appears in `rosterNames` — the basenames of EVERY
|
|
4159
|
+
* declared roster entry, supplied independent of reachability. The reachability-
|
|
4160
|
+
* independent set is load-bearing: a roster repo whose path transiently fails to
|
|
4161
|
+
* resolve (an unmounted volume, a mid-edit symlinked parent, an uncloned sibling)
|
|
4162
|
+
* still owns its view link name, so its live link is never mislabeled a stray and
|
|
4163
|
+
* pruned. (A roster repo's link reached under a DIFFERENT name — e.g. an aliased
|
|
4164
|
+
* roster path — is excluded by the caller before it reaches `existing`, by matching
|
|
4165
|
+
* the link's resolved target against the roster's resolved repos.) `existing` and
|
|
4166
|
+
* `rosterNames` default to empty, so a caller that does not scan the view gets the
|
|
4167
|
+
* original create-only plan.
|
|
4168
|
+
*
|
|
4169
|
+
* Robustness (mirroring the instruction-symlink planner):
|
|
4170
|
+
* - Facts are deduped by normalized path (a repo declared twice yields one link,
|
|
4171
|
+
* never a duplicate `symlinkSync` → EEXIST or a duplicate report entry).
|
|
4172
|
+
* - `ok` is true only when there is genuinely nothing to do, every repo was
|
|
4173
|
+
* resolvable and unambiguous, and the view carries no stray.
|
|
4174
|
+
*/
|
|
4175
|
+
declare function planWorkspaceView(facts: ViewRepoFact[], existing?: ExistingViewLink[], rosterNames?: string[]): WorkspaceViewPlan;
|
|
4176
|
+
|
|
3203
4177
|
/**
|
|
3204
4178
|
* Schema version of the on-disk Basou v0.1 formats these JSON Schemas describe.
|
|
3205
4179
|
* It tracks {@link SchemaVersionSchema} (the `schema_version` field), NOT the
|
|
@@ -3668,6 +4642,126 @@ type ReportRendererResult = {
|
|
|
3668
4642
|
*/
|
|
3669
4643
|
declare function renderReport(input: ReportRendererInput): Promise<ReportRendererResult>;
|
|
3670
4644
|
|
|
4645
|
+
/**
|
|
4646
|
+
* Review-gap surfacer: a read-only, advisory check for the "external
|
|
4647
|
+
* adversarial review before commit" protocol. For each unit of work that landed
|
|
4648
|
+
* commits, it asks whether a CROSS-MODEL review session (a different vendor than
|
|
4649
|
+
* the one that wrote the code — here: Codex) actually examined that repo's diff
|
|
4650
|
+
* before the commit.
|
|
4651
|
+
*
|
|
4652
|
+
* Hard design rule, learned from killing the naive time-window v1 (which
|
|
4653
|
+
* false-cleared the very omission that motivated this): it NEVER emits a
|
|
4654
|
+
* confident "reviewed / clear" verdict. Temporal proximity is not binding. The
|
|
4655
|
+
* worst failure mode is falsely reassuring the operator that a protocol was
|
|
4656
|
+
* followed when it was not, so this surfaces SUSPICION and leaves the final
|
|
4657
|
+
* binding to a human:
|
|
4658
|
+
*
|
|
4659
|
+
* - `omission` no cross-model review of this repo in the preceding window.
|
|
4660
|
+
* - `near_unbound` a review session was nearby but did not examine this repo's
|
|
4661
|
+
* diff or any changed file (the exact class naive v1 cleared).
|
|
4662
|
+
* - `candidate` a review session examined this repo's diff / overlapping
|
|
4663
|
+
* files — listed for the human to confirm it covered THIS
|
|
4664
|
+
* change. NOT an automatic pass.
|
|
4665
|
+
* - `unknown` the repo or time could not be derived; abstain rather than
|
|
4666
|
+
* guess (an abstention is never counted as a clear).
|
|
4667
|
+
*
|
|
4668
|
+
* It reads only captured provenance and writes nothing.
|
|
4669
|
+
*/
|
|
4670
|
+
type ReviewGapVerdict = "omission" | "near_unbound" | "candidate" | "unknown";
|
|
4671
|
+
/** A cross-model review session cited as (possibly) covering a unit of work. */
|
|
4672
|
+
type CitedReview = {
|
|
4673
|
+
sessionId: string;
|
|
4674
|
+
/** The session ran `git diff` / `git show` in the repo (examined the diff). */
|
|
4675
|
+
examinedDiff: boolean;
|
|
4676
|
+
/** Basenames of files the session read/inspected in the repo (capped). */
|
|
4677
|
+
files: string[];
|
|
4678
|
+
endedAt: string | null;
|
|
4679
|
+
};
|
|
4680
|
+
/** One unit of work (a committing session's commits in one repo) and its verdict. */
|
|
4681
|
+
type ReviewGapUnit = {
|
|
4682
|
+
repo: string;
|
|
4683
|
+
/** The session whose commits form this unit. */
|
|
4684
|
+
sessionId: string;
|
|
4685
|
+
commitCount: number;
|
|
4686
|
+
firstCommitAt: string | null;
|
|
4687
|
+
lastCommitAt: string | null;
|
|
4688
|
+
verdict: ReviewGapVerdict;
|
|
4689
|
+
/** For `candidate` / `near_unbound`: the review sessions considered. */
|
|
4690
|
+
reviews: CitedReview[];
|
|
4691
|
+
};
|
|
4692
|
+
type ReviewGapRepoSummary = {
|
|
4693
|
+
repo: string;
|
|
4694
|
+
units: number;
|
|
4695
|
+
omissionUnits: number;
|
|
4696
|
+
nearUnboundUnits: number;
|
|
4697
|
+
candidateUnits: number;
|
|
4698
|
+
unknownUnits: number;
|
|
4699
|
+
};
|
|
4700
|
+
type ReviewGapsSummary = {
|
|
4701
|
+
generatedAt: string;
|
|
4702
|
+
windowHours: number;
|
|
4703
|
+
/** Repos the scope was restricted to, or null when every repo was considered. */
|
|
4704
|
+
scope: string[] | null;
|
|
4705
|
+
repos: ReviewGapRepoSummary[];
|
|
4706
|
+
/** Units WITHOUT a binding review trail (omission + near_unbound), recent-first. */
|
|
4707
|
+
gaps: ReviewGapUnit[];
|
|
4708
|
+
/** Units WITH a review candidate, recent-first (surfaced for confirmation). */
|
|
4709
|
+
candidates: ReviewGapUnit[];
|
|
4710
|
+
/** Units whose repo/time could not be derived from the captured command; abstained, not cleared. */
|
|
4711
|
+
unknowns: ReviewGapUnit[];
|
|
4712
|
+
/** Newest captured commit considered; commits not yet imported are invisible. */
|
|
4713
|
+
newestCommitAt: string | null;
|
|
4714
|
+
};
|
|
4715
|
+
/**
|
|
4716
|
+
* Normalize a path to a stable BINDING key: the canonical full path (NOT just a
|
|
4717
|
+
* basename), so a commit in `/u/projects/basou` and a review in
|
|
4718
|
+
* `/u/projects/basou` bind, while a same-named checkout elsewhere
|
|
4719
|
+
* (`/tmp/x/basou`) does not.
|
|
4720
|
+
*
|
|
4721
|
+
* A workspace "view" reaches sibling repos through symlinks
|
|
4722
|
+
* (`<view>/<repo> -> ../<repo>`), and commits are often run with
|
|
4723
|
+
* `cd <view>/<repo>`. To collapse the view-routed path and the direct path to
|
|
4724
|
+
* one key REGARDLESS of the view directory's name, the path is resolved with
|
|
4725
|
+
* realpath (which also unifies platform aliases such as macOS `/tmp` ->
|
|
4726
|
+
* `/private/tmp`). Only absolute paths are resolved; a relative `cd ../x` target
|
|
4727
|
+
* would realpath against the wrong base, so it is left to the fallback.
|
|
4728
|
+
*
|
|
4729
|
+
* A resolved path is accepted as a key only when it is an actual git repo root
|
|
4730
|
+
* (contains `.git`); a real but non-repo directory (a view root, `/tmp`, a
|
|
4731
|
+
* scratch dir) returns null so the caller abstains (`unknown`) rather than
|
|
4732
|
+
* mislabeling it a repo. When realpath cannot resolve the path (e.g. a historical
|
|
4733
|
+
* capture whose repo has since moved), it FALLS BACK to a string heuristic that
|
|
4734
|
+
* collapses a `*-workspace`-named view and rejects the view root itself. Returns
|
|
4735
|
+
* null for a non-repo / view root, an unexpanded shell var, or empty input.
|
|
4736
|
+
*
|
|
4737
|
+
* The realpath / `.git` probes are the only filesystem I/O this otherwise
|
|
4738
|
+
* string-pure key function performs, and their results are cached for the
|
|
4739
|
+
* process lifetime.
|
|
4740
|
+
*/
|
|
4741
|
+
declare function normalizeRepoPath(p: string | null | undefined): string | null;
|
|
4742
|
+
/**
|
|
4743
|
+
* Short repo key (the final path segment) for DISPLAY and `--scope` matching.
|
|
4744
|
+
* Binding uses {@link normalizeRepoPath} to avoid basename collisions; this is
|
|
4745
|
+
* only the human-facing label.
|
|
4746
|
+
*/
|
|
4747
|
+
declare function normalizeRepoKey(p: string | null | undefined): string | null;
|
|
4748
|
+
type ReviewGapsInput = {
|
|
4749
|
+
paths: BasouPaths;
|
|
4750
|
+
/** ISO "now"; basis for `generatedAt`. */
|
|
4751
|
+
nowIso: string;
|
|
4752
|
+
/** Restrict to these repo keys (e.g. ["basou"]); omit/empty = every repo seen. */
|
|
4753
|
+
scope?: string[];
|
|
4754
|
+
/** Coarse pre-filter window before a commit to look for a review; default 24h. */
|
|
4755
|
+
windowHours?: number;
|
|
4756
|
+
onWarning?: (warning: ReplayWarning, sessionId: string) => void;
|
|
4757
|
+
onSessionSkip?: (sessionId: string, reason: SessionSkipReason) => void;
|
|
4758
|
+
};
|
|
4759
|
+
/**
|
|
4760
|
+
* Compute the {@link ReviewGapsSummary} for a workspace. Read-only: reads
|
|
4761
|
+
* captured sessions / events and writes nothing.
|
|
4762
|
+
*/
|
|
4763
|
+
declare function findReviewGaps(input: ReviewGapsInput): Promise<ReviewGapsSummary>;
|
|
4764
|
+
|
|
3671
4765
|
/**
|
|
3672
4766
|
* Internal abstraction over child-process execution.
|
|
3673
4767
|
*
|
|
@@ -4205,4 +5299,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
|
|
|
4205
5299
|
*/
|
|
4206
5300
|
declare const BASOU_CORE_VERSION = "0.1.0";
|
|
4207
5301
|
|
|
4208
|
-
export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, type PrefixedId, type ProcessRunner, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type ReplayOptions, type ReplayWarning, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type RiskLevel, RiskLevelSchema, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isImportDerivedSource, isLazyExpired, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, overwriteYamlFile, parseDuration, parseMarkers, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, renderDecisions, renderHandoff, renderOrientation, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, tryRemoteUrl, ulid, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
|
|
5302
|
+
export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
|