@lmzhen/dsh-evolution-commands 0.21.0 → 0.23.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.
@@ -1,4 +1,5 @@
1
1
  import { type PlatformServiceProbe, type ScopedProbeReport } from '@lmzhen/dsh-evolution-core';
2
+ import type { MountedProfile } from './profiles.ts';
2
3
  /** P2-20 (audit): the TS-side copy of the install-target tails. The script
3
4
  * side (install-layered.mjs, verify-profile-bundles.mjs) imports the list
4
5
  * from packages/scripts/lib-family-packages.mjs; guard-scripts.spec.ts pins
@@ -79,6 +80,11 @@ export interface DoctorReport {
79
80
  * counterpart (inferred from the install form, P0-1) and may disagree on a
80
81
  * multi-profile machine; the render marks the aggregation explicitly. */
81
82
  bundles: string[];
83
+ /** G3 (0.22): profile directories under this home that are NOT the profile this process runs in.
84
+ * On a one-home-two-planes machine (or after a home clone) they are leftovers from the other
85
+ * plane: the write-path exclusion ignores them, while {@link bundles} still counts them — this
86
+ * list is what makes that difference visible instead of mysterious. */
87
+ foreignProfiles: string[];
82
88
  conflicts: string[];
83
89
  envIssues: string[];
84
90
  /** S0.2 (v37 P0-1): memory files carrying the platform's `{{` prompt-variable
@@ -153,6 +159,7 @@ export declare function diagnose(ctx: {
153
159
  get(name: string): unknown;
154
160
  }, options?: {
155
161
  home?: string;
162
+ mounted?: MountedProfile;
156
163
  }): Promise<DoctorReport>;
157
164
  export declare function renderDoctorText(report: DoctorReport): string;
158
165
  //# sourceMappingURL=doctor.d.ts.map
@@ -12,6 +12,8 @@ declare module '@deepseek-ai/dsh-llm' {
12
12
  } & ContextFormed;
13
13
  }
14
14
  }
15
+ import { atomicWriteFiles } from '@lmzhen/dsh-evolution-core';
16
+ export { atomicWriteFiles };
15
17
  export declare const name = "evolution-commands";
16
18
  export interface Config {
17
19
  /** Skill-tree root for maintain/restructure; empty uses the default tree.
@@ -71,46 +73,4 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
71
73
  threatExemptLabels: z<string[], string[], "defined">;
72
74
  }>>, "plain">;
73
75
  export declare function apply(ctx: Context, rawConfig?: Config): void;
74
- /** F-211: the fs surface atomicWriteFiles uses — injectable so the commit-phase
75
- * rename-failure path is deterministically testable. Defaults to node:fs.
76
- * `mtime` (optional) probes a path's last-write time so the failed-commit
77
- * recovery message can name the generation it restored from. */
78
- interface FsOps {
79
- writeFileSync(path: string, data: string | Uint8Array): void;
80
- existsSync(path: string): boolean;
81
- copyFileSync(from: string, to: string): void;
82
- renameSync(from: string, to: string): void;
83
- rmSync(path: string, options?: {
84
- force?: boolean;
85
- recursive?: boolean;
86
- }): void;
87
- mtime?(path: string): number | null;
88
- }
89
- /**
90
- * S6.3 (E-40): atomically replace a set of files within one directory.
91
- * Each file is staged to a sibling `<name>.tmp`, then renamed into place
92
- * (atomic on the same filesystem); a pre-existing file keeps a single
93
- * `<name>.bak`. A failure at ANY staging step removes the staged temps and
94
- * leaves the previous files untouched — a half-written target is impossible
95
- * up to the staging and backup phases.
96
- *
97
- * F-211: the rename COMMIT phase does not carry the same guarantee. When a
98
- * filesystem refuses to overwrite the destination, the code removes it and
99
- * retries the rename; if the retry ALSO fails the destination is gone. The
100
- * single `.bak` created above still holds the previous content, so the target
101
- * is restored from it (best-effort) before the original rename error is
102
- * rethrown, so a failed commit never leaves the file missing.
103
- *
104
- * V4-17: the `.bak` is refreshed to the committed content after every
105
- * successful commit, so a later failed commit always recovers to the most
106
- * recent good generation (a once-at-first-install `.bak` could otherwise
107
- * restore content several installs old). The recovery error names the `.bak`
108
- * source (and mtime when available) so the caller knows the generation it
109
- * restored from.
110
- */
111
- export declare function atomicWriteFiles(targetDir: string, writes: Array<{
112
- name: string;
113
- content: string | Uint8Array;
114
- }>, fs?: FsOps, logger?: (message: string) => void): void;
115
- export {};
116
76
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,65 @@
1
+ /**
2
+ * F-4/G7.4 (C7): the ONE parser for the /evolution command surface.
3
+ *
4
+ * The surface used to answer each subcommand with its own regex (`^params(?: --group ([a-z-]+))?(?: --json)?$`
5
+ * and nine siblings), so `params --json --group review` and `params --group=review` were refused as
6
+ * "not a subcommand", while a sibling regex accepted `=` — and a mistyped group name degraded into the
7
+ * generic error instead of the one that lists the known groups.
8
+ *
9
+ * This parser is order- and `=`-tolerant, matches the subcommand name case-insensitively, keeps a flag
10
+ * VALUE's case intact (the domain layer is the one that validates it), and answers a SYNTAX failure when a
11
+ * declared value flag is left without its value (F-3: `policy set a.b --expect 3` must not read
12
+ * `--expect 3` as the value).
13
+ *
14
+ * @module @lmzhen/dsh-evolution-commands/parse
15
+ */
16
+ /** The flags whose declared form takes a value (`--group <name>`, `--expect <revision>`, …). */
17
+ /**
18
+ * Exported so the command-table gate can tie it to the table's usage lines (P2: two declarations of
19
+ * "which flags take a value" would otherwise be free to drift apart).
20
+ */
21
+ export declare const VALUE_FLAGS: readonly string[];
22
+ /** One parsed command line. */
23
+ export interface ParsedCommand {
24
+ /** The subcommand name, lower-cased (the table stores it lower-case too). */
25
+ readonly name: string;
26
+ /** Declared flags: a value when the flag takes one, `true` when it is bare. */
27
+ readonly flags: ReadonlyMap<string, string | true>;
28
+ /** Everything that was neither the name nor a flag, in order. */
29
+ readonly positionals: readonly string[];
30
+ }
31
+ /** A SYNTAX failure — the caller answers it as a usage error, never as a domain error. */
32
+ export interface ParseFailure {
33
+ /** Which syntax rule was broken. */
34
+ readonly kind: 'unknown-subcommand' | 'unknown-flag' | 'missing-value';
35
+ /** The subcommand name the caller saw (empty when the line was empty). */
36
+ readonly name: string;
37
+ /** The flag involved, for the two flag failures. */
38
+ readonly flag?: string;
39
+ }
40
+ /**
41
+ * Narrow a parse result: true when it is the syntax failure rather than a command.
42
+ * @param result - what {@link parseSubcommand} answered.
43
+ * @returns whether the line was refused.
44
+ */
45
+ export declare function isParseFailure(result: ParsedCommand | ParseFailure): result is ParseFailure;
46
+ /**
47
+ * Read one SUB-FORM line: `maintain status`, `maintain report`, … — the shape the regexes used to own.
48
+ *
49
+ * A sub-form matches only when: the line parses, the name matches, the first positional is the sub-form,
50
+ * and every flag the line carries is DECLARED by the table (an undeclared flag keeps the old
51
+ * "unknown arguments" answer instead of silently becoming a bare flag).
52
+ * @param input - the text after `/evolution`.
53
+ * @param name - the subcommand name the table declares (e.g. `maintain`).
54
+ * @param sub - the sub-form word (e.g. `status`).
55
+ * @param declaredFlags - the flags the table declares for `name` (`flagsOf(name)`).
56
+ * @returns the parsed command, or null when this line is not that sub-form.
57
+ */
58
+ export declare function subcommandOf(input: string, name: string, sub: string, declaredFlags: readonly string[]): ParsedCommand | null;
59
+ /**
60
+ * Parse one command line into its name, flags and positionals.
61
+ * @param input - the text after `/evolution`.
62
+ * @returns the parsed command, or the syntax failure that refused it.
63
+ */
64
+ export declare function parseSubcommand(input: string): ParsedCommand | ParseFailure;
65
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1,74 @@
1
+ /** The patch file name inside a profile directory (the platform's own spelling). */
2
+ export declare const PROFILE_PATCH_FILENAME = "cordis.patch.yml";
3
+ /** One profile directory under a home. */
4
+ export interface HomeProfile {
5
+ /** The profile's directory name (what `--profile` spells). */
6
+ readonly profile: string;
7
+ /** Its absolute (or home-relative-joined) directory. */
8
+ readonly dir: string;
9
+ }
10
+ /** The outcome of reading `<home>/profiles`: the entries, or why they could not be read. */
11
+ export type ProfilesScan = {
12
+ readonly kind: 'scanned';
13
+ readonly profiles: readonly HomeProfile[];
14
+ } | {
15
+ readonly kind: 'unreadable';
16
+ readonly error: unknown;
17
+ };
18
+ /** The ONLY directory read of `<home>/profiles` in the family.
19
+ * `profiles/node_modules` is a platform-created directory that never carries a manifest (S2.1), dot-entries
20
+ * are not profiles, and a directory without `package.json` is not one either.
21
+ * @param home - the resolved DSH home.
22
+ * @returns the scanned entries, or the enumeration error (never a silent empty list).
23
+ */
24
+ export declare function scanProfiles(home: string): ProfilesScan;
25
+ /** Every profile directory under one home, by name — the DIAGNOSTIC question (doctor's cross-profile
26
+ * summary). An unreadable home answers the empty list here because doctor reports the failure on its
27
+ * own DEGRADED row; a write path must ask {@link installExclusionTarget} instead.
28
+ * @param home - the resolved DSH home.
29
+ * @returns one entry per profile directory carrying a manifest.
30
+ */
31
+ export declare function homeProfiles(home: string): readonly HomeProfile[];
32
+ /** The bundle names one profile's manifest declares.
33
+ * @param profileDir - the profile directory.
34
+ * @param onError - called with the read/parse failure; a torn manifest is NOT "no bundles".
35
+ * @returns the declared bundle names (empty when the manifest is absent).
36
+ */
37
+ export declare function profileBundles(profileDir: string, onError?: (error: unknown) => void): string[];
38
+ /** One optional file's text, or `''` when it is absent or unreadable.
39
+ * @param path - the file to read.
40
+ * @returns the text.
41
+ */
42
+ export declare function readTextOrEmpty(path: string): string;
43
+ /** The profile's own patch text, or `''` when it has none.
44
+ * @param profileDir - the profile directory.
45
+ * @returns the patch text.
46
+ */
47
+ export declare function profilePatchText(profileDir: string): string;
48
+ /** The profile this PROCESS is mounted in, as the write-path exclusion must see it. */
49
+ export interface MountedProfile {
50
+ /** The mounted profile's name. */
51
+ readonly profile: string;
52
+ /** Its directory. */
53
+ readonly dir: string;
54
+ /** The bundles this plane actually STARTED (the platform's `startedBundles`). */
55
+ readonly bundles: readonly string[];
56
+ }
57
+ /** Where the preset-install exclusion must look: THIS plane only, or an explicit refusal.
58
+ *
59
+ * The write path does NOT scan the home: the mounted profile is the whole question (its started
60
+ * bundles plus the ones its own manifest declares — either can double-mount), and a question that
61
+ * cannot be answered still REFUSES. That is what makes a leftover profile from the other plane
62
+ * harmless here: it is not this plane's profile, so it is never consulted.
63
+ * @param mounted - the mounted profile, or undefined outside a profile (a bare library mount).
64
+ * @returns the profile to judge with its bundle rows, or the reason a refusal is mandatory.
65
+ */
66
+ export declare function installExclusionTarget(mounted: MountedProfile | undefined): {
67
+ readonly kind: 'mounted';
68
+ readonly profile: HomeProfile;
69
+ readonly bundles: readonly string[];
70
+ } | {
71
+ readonly kind: 'undecidable';
72
+ readonly reason: string;
73
+ };
74
+ //# sourceMappingURL=profiles.d.ts.map
@@ -12,9 +12,17 @@
12
12
  export interface CommandEntry {
13
13
  /** The subcommand as typed after `/evolution`. Variants use `|` (curator run|pause). */
14
14
  usage: string;
15
+ /** C7: true when the subcommand only READS (no skill/param write). Stored: not derivable. */
16
+ readonly: boolean;
15
17
  /** One-line purpose rendered in the help output. */
16
18
  summary: string;
17
19
  }
20
+ /** The flags the TABLE declares for one subcommand (name → `--flag`), DERIVED from its usage. */
21
+ export declare function flagsOf(name: string): string[];
22
+ /** C7 shape, DERIVED: the subcommand name is the first token of `usage` (variants use `|`). */
23
+ export declare function entryName(entry: CommandEntry): string;
24
+ /** C7 shape, DERIVED: the flags a `usage` declares, in the form `--flag` (value flags keep their name). */
25
+ export declare function entryFlags(entry: CommandEntry): string[];
18
26
  export declare const COMMAND_ENTRIES: CommandEntry[];
19
27
  /** The input-declaration hint (single line, ` | `-separated). */
20
28
  export declare function renderHint(): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-commands",
3
3
  "description": "Human commands for the evolution family (/evolution pending|curator|maintain|doctor) (community build)",
4
- "version": "0.21.0",
4
+ "version": "0.23.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,17 +27,18 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@deepseek-ai/schemastery": "^3.18.4",
30
- "@lmzhen/dsh-evolution-approval": "^0.21.0",
31
- "@lmzhen/dsh-evolution-core": "^0.21.0",
32
- "@lmzhen/dsh-evolution-maintenance": "^0.21.0"
30
+ "@lmzhen/dsh-evolution-approval": "^0.23.0",
31
+ "@lmzhen/dsh-evolution-core": "^0.23.0",
32
+ "@lmzhen/dsh-evolution-maintenance": "^0.23.0"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@lmzhen/dsh-evolution-agent-preset": "^0.21.0"
35
+ "@lmzhen/dsh-evolution-agent-preset": "^0.23.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@deepseek-ai/cordis": "^4.0.1",
39
39
  "@deepseek-ai/dsh-commands": "^0.2.0-rc.1",
40
- "@deepseek-ai/dsh-llm": "^0.2.0-rc.1"
40
+ "@deepseek-ai/dsh-llm": "^0.2.0-rc.1",
41
+ "@lmzhen/dsh-evolution-io": "^0.23.0"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@deepseek-ai/dsh-commands": "^0.2.0-rc.1"