skill-flow 1.0.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.
Files changed (87) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +108 -0
  3. package/README.zh.md +108 -0
  4. package/dist/adapters/channel-adapters.d.ts +8 -0
  5. package/dist/adapters/channel-adapters.js +56 -0
  6. package/dist/adapters/channel-adapters.js.map +1 -0
  7. package/dist/cli.d.ts +2 -0
  8. package/dist/cli.js +118 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/domain/types.d.ts +133 -0
  11. package/dist/domain/types.js +2 -0
  12. package/dist/domain/types.js.map +1 -0
  13. package/dist/services/deployment-applier.d.ts +6 -0
  14. package/dist/services/deployment-applier.js +54 -0
  15. package/dist/services/deployment-applier.js.map +1 -0
  16. package/dist/services/deployment-planner.d.ts +11 -0
  17. package/dist/services/deployment-planner.js +179 -0
  18. package/dist/services/deployment-planner.js.map +1 -0
  19. package/dist/services/doctor-service.d.ts +5 -0
  20. package/dist/services/doctor-service.js +129 -0
  21. package/dist/services/doctor-service.js.map +1 -0
  22. package/dist/services/inventory-service.d.ts +14 -0
  23. package/dist/services/inventory-service.js +186 -0
  24. package/dist/services/inventory-service.js.map +1 -0
  25. package/dist/services/skill-flow.d.ts +60 -0
  26. package/dist/services/skill-flow.js +260 -0
  27. package/dist/services/skill-flow.js.map +1 -0
  28. package/dist/services/source-service.d.ts +35 -0
  29. package/dist/services/source-service.js +270 -0
  30. package/dist/services/source-service.js.map +1 -0
  31. package/dist/services/workflow-service.d.ts +5 -0
  32. package/dist/services/workflow-service.js +32 -0
  33. package/dist/services/workflow-service.js.map +1 -0
  34. package/dist/state/store.d.ts +14 -0
  35. package/dist/state/store.js +59 -0
  36. package/dist/state/store.js.map +1 -0
  37. package/dist/tests/skill-flow.test.d.ts +1 -0
  38. package/dist/tests/skill-flow.test.js +926 -0
  39. package/dist/tests/skill-flow.test.js.map +1 -0
  40. package/dist/tui/config-app.d.ts +47 -0
  41. package/dist/tui/config-app.js +732 -0
  42. package/dist/tui/config-app.js.map +1 -0
  43. package/dist/tui/selection-state.d.ts +8 -0
  44. package/dist/tui/selection-state.js +32 -0
  45. package/dist/tui/selection-state.js.map +1 -0
  46. package/dist/utils/constants.d.ts +19 -0
  47. package/dist/utils/constants.js +164 -0
  48. package/dist/utils/constants.js.map +1 -0
  49. package/dist/utils/format.d.ts +6 -0
  50. package/dist/utils/format.js +45 -0
  51. package/dist/utils/format.js.map +1 -0
  52. package/dist/utils/fs.d.ts +10 -0
  53. package/dist/utils/fs.js +89 -0
  54. package/dist/utils/fs.js.map +1 -0
  55. package/dist/utils/git.d.ts +3 -0
  56. package/dist/utils/git.js +12 -0
  57. package/dist/utils/git.js.map +1 -0
  58. package/dist/utils/result.d.ts +4 -0
  59. package/dist/utils/result.js +15 -0
  60. package/dist/utils/result.js.map +1 -0
  61. package/dist/utils/source-id.d.ts +2 -0
  62. package/dist/utils/source-id.js +16 -0
  63. package/dist/utils/source-id.js.map +1 -0
  64. package/img/img-1.jpg +0 -0
  65. package/package.json +39 -0
  66. package/src/adapters/channel-adapters.ts +75 -0
  67. package/src/cli.tsx +147 -0
  68. package/src/domain/types.ts +175 -0
  69. package/src/services/deployment-applier.ts +81 -0
  70. package/src/services/deployment-planner.ts +259 -0
  71. package/src/services/doctor-service.ts +156 -0
  72. package/src/services/inventory-service.ts +251 -0
  73. package/src/services/skill-flow.ts +381 -0
  74. package/src/services/source-service.ts +427 -0
  75. package/src/services/workflow-service.ts +56 -0
  76. package/src/state/store.ts +68 -0
  77. package/src/tests/skill-flow.test.ts +1184 -0
  78. package/src/tui/config-app.tsx +1094 -0
  79. package/src/tui/selection-state.ts +45 -0
  80. package/src/utils/constants.ts +201 -0
  81. package/src/utils/format.ts +59 -0
  82. package/src/utils/fs.ts +102 -0
  83. package/src/utils/git.ts +16 -0
  84. package/src/utils/result.ts +23 -0
  85. package/src/utils/source-id.ts +19 -0
  86. package/tsconfig.json +22 -0
  87. package/vitest.config.ts +8 -0
@@ -0,0 +1,133 @@
1
+ export type Warning = {
2
+ code: string;
3
+ message: string;
4
+ };
5
+ export type Failure = {
6
+ code: string;
7
+ message: string;
8
+ };
9
+ export type Result<T> = {
10
+ ok: true;
11
+ data: T;
12
+ warnings: Warning[];
13
+ errors: [];
14
+ } | {
15
+ ok: false;
16
+ data?: T;
17
+ warnings: Warning[];
18
+ errors: Failure[];
19
+ };
20
+ export type SourceKind = "git";
21
+ export type DeploymentTargetName = "claude-code" | "codex" | "cursor" | "github-copilot" | "gemini-cli" | "opencode" | "openclaw" | "pi" | "windsurf" | "roo-code" | "cline" | "amp" | "kiro";
22
+ export type DeploymentStrategy = "symlink" | "copy";
23
+ export type HealthStatus = "HEALTHY" | "ACTIVE" | "INACTIVE" | "PARTIAL" | "BLOCKED" | "INVALID" | "UPDATE AVAILABLE" | "UP TO DATE" | "DRIFT DETECTED";
24
+ export type SourceManifestRecord = {
25
+ id: string;
26
+ locator: string;
27
+ kind: SourceKind;
28
+ displayName: string;
29
+ addedAt: string;
30
+ };
31
+ export type TargetBinding = {
32
+ enabled: boolean;
33
+ leafIds: string[];
34
+ };
35
+ export type SourceBinding = {
36
+ targets: Partial<Record<DeploymentTargetName, TargetBinding>>;
37
+ };
38
+ export type Manifest = {
39
+ schemaVersion: 1;
40
+ sources: SourceManifestRecord[];
41
+ bindings: Record<string, SourceBinding>;
42
+ };
43
+ export type InvalidLeafRecord = {
44
+ path: string;
45
+ reason: string;
46
+ };
47
+ export type SourceLockRecord = {
48
+ id: string;
49
+ locator: string;
50
+ kind: SourceKind;
51
+ displayName: string;
52
+ checkoutPath: string;
53
+ commitSha: string;
54
+ updatedAt: string;
55
+ leafIds: string[];
56
+ invalidLeafs: InvalidLeafRecord[];
57
+ };
58
+ export type LeafRecord = {
59
+ id: string;
60
+ sourceId: string;
61
+ name: string;
62
+ linkName: string;
63
+ title: string;
64
+ description: string;
65
+ relativePath: string;
66
+ absolutePath: string;
67
+ skillFilePath: string;
68
+ contentHash: string;
69
+ metadataWarnings: string[];
70
+ valid: true;
71
+ };
72
+ export type DeploymentRecord = {
73
+ sourceId: string;
74
+ leafId: string;
75
+ target: DeploymentTargetName;
76
+ targetPath: string;
77
+ strategy: DeploymentStrategy;
78
+ status: "active" | "drifted" | "blocked" | "removed";
79
+ contentHash: string;
80
+ appliedAt: string;
81
+ };
82
+ export type LockFile = {
83
+ schemaVersion: 1;
84
+ sources: SourceLockRecord[];
85
+ leafInventory: LeafRecord[];
86
+ deployments: DeploymentRecord[];
87
+ };
88
+ export type ChannelDetection = {
89
+ target: DeploymentTargetName;
90
+ strategy: DeploymentStrategy;
91
+ available: boolean;
92
+ rootPath: string;
93
+ reason?: string;
94
+ };
95
+ export type DeploymentActionKind = "create" | "update" | "remove" | "noop" | "blocked";
96
+ export type DeploymentAction = {
97
+ kind: DeploymentActionKind;
98
+ sourceId: string;
99
+ leafId: string;
100
+ target: DeploymentTargetName;
101
+ strategy: DeploymentStrategy;
102
+ sourcePath: string;
103
+ targetPath: string;
104
+ previousTargetPath?: string;
105
+ reason?: string;
106
+ contentHash: string;
107
+ };
108
+ export type DeploymentPlan = {
109
+ actions: DeploymentAction[];
110
+ warnings: Warning[];
111
+ blocked: DeploymentAction[];
112
+ };
113
+ export type DoctorIssueSeverity = "info" | "warning" | "error";
114
+ export type DoctorIssue = {
115
+ severity: DoctorIssueSeverity;
116
+ sourceId: string;
117
+ target?: DeploymentTargetName;
118
+ leafId?: string;
119
+ code: string;
120
+ message: string;
121
+ };
122
+ export type DoctorReport = {
123
+ status: "HEALTHY" | "PARTIAL" | "BLOCKED";
124
+ issues: DoctorIssue[];
125
+ };
126
+ export type WorkflowSummary = {
127
+ source: SourceManifestRecord;
128
+ lock: SourceLockRecord | undefined;
129
+ leafs: LeafRecord[];
130
+ bindings: SourceBinding;
131
+ activeTargetCount: number;
132
+ health: HealthStatus;
133
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/domain/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { DeploymentAction, LockFile, Result } from "../domain/types.js";
2
+ export declare class DeploymentApplier {
3
+ applyPlan(lockFile: LockFile, actions: DeploymentAction[]): Promise<Result<{
4
+ applied: DeploymentAction[];
5
+ }>>;
6
+ }
@@ -0,0 +1,54 @@
1
+ import path from "node:path";
2
+ import { copyDirectory, createSymlink, ensureDir, pathExists, removePath } from "../utils/fs.js";
3
+ import { ok } from "../utils/result.js";
4
+ export class DeploymentApplier {
5
+ async applyPlan(lockFile, actions) {
6
+ const applied = [];
7
+ for (const action of actions) {
8
+ if (action.kind === "blocked" || action.kind === "noop") {
9
+ continue;
10
+ }
11
+ if (action.kind === "remove") {
12
+ if (await pathExists(action.targetPath)) {
13
+ await removePath(action.targetPath);
14
+ }
15
+ lockFile.deployments = lockFile.deployments.filter((deployment) => !(deployment.sourceId === action.sourceId &&
16
+ deployment.leafId === action.leafId &&
17
+ deployment.target === action.target));
18
+ applied.push(action);
19
+ continue;
20
+ }
21
+ await ensureDir(path.dirname(action.targetPath));
22
+ if (action.previousTargetPath &&
23
+ action.previousTargetPath !== action.targetPath &&
24
+ (await pathExists(action.previousTargetPath))) {
25
+ await removePath(action.previousTargetPath);
26
+ }
27
+ if (action.strategy === "symlink") {
28
+ await createSymlink(action.sourcePath, action.targetPath);
29
+ }
30
+ else {
31
+ await copyDirectory(action.sourcePath, action.targetPath);
32
+ }
33
+ const nextRecord = {
34
+ sourceId: action.sourceId,
35
+ leafId: action.leafId,
36
+ target: action.target,
37
+ targetPath: action.targetPath,
38
+ strategy: action.strategy,
39
+ status: "active",
40
+ contentHash: action.contentHash,
41
+ appliedAt: new Date().toISOString(),
42
+ };
43
+ lockFile.deployments = [
44
+ ...lockFile.deployments.filter((deployment) => !(deployment.sourceId === action.sourceId &&
45
+ deployment.leafId === action.leafId &&
46
+ deployment.target === action.target)),
47
+ nextRecord,
48
+ ];
49
+ applied.push(action);
50
+ }
51
+ return ok({ applied });
52
+ }
53
+ }
54
+ //# sourceMappingURL=deployment-applier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-applier.js","sourceRoot":"","sources":["../../src/services/deployment-applier.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjG,OAAO,EAAE,EAAE,EAAE,MAAM,oBAAoB,CAAC;AAExC,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,SAAS,CACb,QAAkB,EAClB,OAA2B;QAE3B,MAAM,OAAO,GAAuB,EAAE,CAAC;QAEvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxD,SAAS;YACX,CAAC;YAED,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,IAAI,MAAM,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;oBACxC,MAAM,UAAU,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;gBACtC,CAAC;gBACD,QAAQ,CAAC,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAChD,CAAC,UAAU,EAAE,EAAE,CACb,CAAC,CACC,UAAU,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;oBACvC,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;oBACnC,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CACpC,CACJ,CAAC;gBACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACjD,IACE,MAAM,CAAC,kBAAkB;gBACzB,MAAM,CAAC,kBAAkB,KAAK,MAAM,CAAC,UAAU;gBAC/C,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC,EAC7C,CAAC;gBACD,MAAM,UAAU,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAC;YAC9C,CAAC;YAED,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClC,MAAM,aAAa,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YAC5D,CAAC;iBAAM,CAAC;gBACN,MAAM,aAAa,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;YAC5D,CAAC;YAED,MAAM,UAAU,GAAqB;gBACnC,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,MAAM,EAAE,QAAQ;gBAChB,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC;YAEF,QAAQ,CAAC,WAAW,GAAG;gBACrB,GAAG,QAAQ,CAAC,WAAW,CAAC,MAAM,CAC5B,CAAC,UAAU,EAAE,EAAE,CACb,CAAC,CACC,UAAU,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ;oBACvC,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;oBACnC,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CACpC,CACJ;gBACD,UAAU;aACX,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QAED,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ import type { DeploymentPlan, LockFile, Manifest, Result } from "../domain/types.js";
2
+ import type { ChannelAdapter } from "../adapters/channel-adapters.js";
3
+ export declare class DeploymentPlanner {
4
+ private readonly adapters;
5
+ constructor(adapters: ChannelAdapter[]);
6
+ planForSource(sourceId: string, manifest: Manifest, lockFile: LockFile): Promise<Result<DeploymentPlan>>;
7
+ private planTarget;
8
+ private resolveDesiredAction;
9
+ private inspectTargetPath;
10
+ private buildProjectedLinkNameMap;
11
+ }
@@ -0,0 +1,179 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { ok } from "../utils/result.js";
4
+ export class DeploymentPlanner {
5
+ adapters;
6
+ constructor(adapters) {
7
+ this.adapters = adapters;
8
+ }
9
+ async planForSource(sourceId, manifest, lockFile) {
10
+ const binding = manifest.bindings[sourceId] ?? { targets: {} };
11
+ const leafs = lockFile.leafInventory.filter((leaf) => leaf.sourceId === sourceId);
12
+ const previousDeployments = lockFile.deployments.filter((deployment) => deployment.sourceId === sourceId);
13
+ const actions = [];
14
+ const warnings = [];
15
+ for (const adapter of this.adapters) {
16
+ const detection = await adapter.detect();
17
+ const targetBinding = binding.targets[adapter.target];
18
+ const desiredLeafIds = targetBinding?.enabled === true ? new Set(targetBinding.leafIds) : new Set();
19
+ const projectedLinkNames = this.buildProjectedLinkNameMap(manifest, lockFile, adapter.target);
20
+ const plannedForTarget = await this.planTarget(sourceId, adapter, detection.available, detection.rootPath, detection.reason, desiredLeafIds, leafs, previousDeployments, projectedLinkNames);
21
+ actions.push(...plannedForTarget.actions);
22
+ warnings.push(...plannedForTarget.warnings);
23
+ }
24
+ return ok({
25
+ actions,
26
+ warnings,
27
+ blocked: actions.filter((action) => action.kind === "blocked"),
28
+ });
29
+ }
30
+ // fetch -> scan -> diff -> replan -> reapply
31
+ //
32
+ // desired bindings + lock state + disk state
33
+ // -> create | update | remove | noop | blocked
34
+ async planTarget(sourceId, adapter, targetAvailable, rootPath, unavailableReason, desiredLeafIds, leafs, previousDeployments, projectedLinkNames) {
35
+ const actions = [];
36
+ const warnings = [];
37
+ const desiredLeafs = leafs.filter((leaf) => desiredLeafIds.has(leaf.id));
38
+ const deploymentsForTarget = previousDeployments.filter((deployment) => deployment.target === adapter.target);
39
+ const managedByLeafId = new Map(deploymentsForTarget.map((deployment) => [deployment.leafId, deployment]));
40
+ const missingDesiredLeafIds = [...desiredLeafIds].filter((leafId) => !desiredLeafs.some((leaf) => leaf.id === leafId));
41
+ for (const missingLeafId of missingDesiredLeafIds) {
42
+ const existing = managedByLeafId.get(missingLeafId);
43
+ warnings.push({
44
+ code: "MISSING_LEAF_SELECTION",
45
+ message: `${missingLeafId} no longer exists in source inventory.`,
46
+ });
47
+ if (existing) {
48
+ actions.push({
49
+ kind: "remove",
50
+ sourceId,
51
+ leafId: missingLeafId,
52
+ target: existing.target,
53
+ strategy: existing.strategy,
54
+ sourcePath: "",
55
+ targetPath: existing.targetPath,
56
+ contentHash: existing.contentHash,
57
+ reason: "Selected leaf no longer exists in source inventory.",
58
+ });
59
+ }
60
+ }
61
+ for (const leaf of desiredLeafs) {
62
+ const existing = managedByLeafId.get(leaf.id);
63
+ const projectedLinkName = projectedLinkNames.get(leaf.id) ?? leaf.linkName;
64
+ const targetPath = adapter.resolveTargetPath(rootPath, projectedLinkName);
65
+ if (!targetAvailable) {
66
+ const blockedAction = {
67
+ kind: "blocked",
68
+ sourceId,
69
+ leafId: leaf.id,
70
+ target: adapter.target,
71
+ strategy: adapter.strategy,
72
+ sourcePath: leaf.absolutePath,
73
+ targetPath,
74
+ contentHash: leaf.contentHash,
75
+ ...(unavailableReason ? { reason: unavailableReason } : {}),
76
+ };
77
+ actions.push(blockedAction);
78
+ continue;
79
+ }
80
+ const diskState = await this.inspectTargetPath(targetPath, leaf.absolutePath);
81
+ if (diskState.foreign && !existing) {
82
+ actions.push({
83
+ kind: "blocked",
84
+ sourceId,
85
+ leafId: leaf.id,
86
+ target: adapter.target,
87
+ strategy: adapter.strategy,
88
+ sourcePath: leaf.absolutePath,
89
+ targetPath,
90
+ reason: "Foreign content already exists at target path.",
91
+ contentHash: leaf.contentHash,
92
+ });
93
+ continue;
94
+ }
95
+ const kind = this.resolveDesiredAction(existing, diskState.matchesExpected, leaf);
96
+ actions.push({
97
+ kind,
98
+ sourceId,
99
+ leafId: leaf.id,
100
+ target: adapter.target,
101
+ strategy: adapter.strategy,
102
+ sourcePath: leaf.absolutePath,
103
+ targetPath,
104
+ ...(existing && existing.targetPath !== targetPath
105
+ ? { previousTargetPath: existing.targetPath }
106
+ : {}),
107
+ contentHash: leaf.contentHash,
108
+ });
109
+ }
110
+ for (const deployment of deploymentsForTarget) {
111
+ if (desiredLeafIds.has(deployment.leafId)) {
112
+ continue;
113
+ }
114
+ actions.push({
115
+ kind: "remove",
116
+ sourceId,
117
+ leafId: deployment.leafId,
118
+ target: deployment.target,
119
+ strategy: deployment.strategy,
120
+ sourcePath: "",
121
+ targetPath: deployment.targetPath,
122
+ contentHash: deployment.contentHash,
123
+ });
124
+ }
125
+ return { actions, warnings, blocked: actions.filter((item) => item.kind === "blocked") };
126
+ }
127
+ resolveDesiredAction(existing, matchesExpected, leaf) {
128
+ if (!existing) {
129
+ return matchesExpected ? "noop" : "create";
130
+ }
131
+ if (!matchesExpected) {
132
+ return "update";
133
+ }
134
+ return existing.contentHash === leaf.contentHash ? "noop" : "update";
135
+ }
136
+ async inspectTargetPath(targetPath, expectedSourcePath) {
137
+ try {
138
+ const stats = await fs.lstat(targetPath);
139
+ if (stats.isSymbolicLink()) {
140
+ const linked = await fs.readlink(targetPath);
141
+ const resolved = path.resolve(path.dirname(targetPath), linked);
142
+ const matchesExpected = resolved === expectedSourcePath;
143
+ return { exists: true, matchesExpected, foreign: !matchesExpected };
144
+ }
145
+ return { exists: true, matchesExpected: false, foreign: true };
146
+ }
147
+ catch {
148
+ return { exists: false, matchesExpected: false, foreign: false };
149
+ }
150
+ }
151
+ buildProjectedLinkNameMap(manifest, lockFile, target) {
152
+ const selectedLeafs = manifest.sources.flatMap((source) => {
153
+ const targetBinding = manifest.bindings[source.id]?.targets[target];
154
+ if (!targetBinding?.enabled) {
155
+ return [];
156
+ }
157
+ return targetBinding.leafIds
158
+ .map((leafId) => lockFile.leafInventory.find((leaf) => leaf.id === leafId))
159
+ .filter((leaf) => Boolean(leaf));
160
+ });
161
+ const byLinkName = new Map();
162
+ for (const leaf of selectedLeafs) {
163
+ const group = byLinkName.get(leaf.linkName) ?? [];
164
+ group.push(leaf);
165
+ byLinkName.set(leaf.linkName, group);
166
+ }
167
+ const result = new Map();
168
+ for (const leaf of selectedLeafs) {
169
+ const collisions = byLinkName.get(leaf.linkName) ?? [];
170
+ if (collisions.length <= 1) {
171
+ result.set(leaf.id, leaf.linkName);
172
+ continue;
173
+ }
174
+ result.set(leaf.id, `${leaf.sourceId}-${leaf.linkName}`);
175
+ }
176
+ return result;
177
+ }
178
+ }
179
+ //# sourceMappingURL=deployment-planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment-planner.js","sourceRoot":"","sources":["../../src/services/deployment-planner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { DoctorReport, LockFile, Manifest, Result } from "../domain/types.js";
2
+ export declare class DoctorService {
3
+ private readonly adapters;
4
+ run(manifest: Manifest, lockFile: LockFile): Promise<Result<DoctorReport>>;
5
+ }
@@ -0,0 +1,129 @@
1
+ import fs from "node:fs/promises";
2
+ import { createChannelAdapters } from "../adapters/channel-adapters.js";
3
+ import { hashDirectory, isBrokenSymlink, pathExists } from "../utils/fs.js";
4
+ import { ok } from "../utils/result.js";
5
+ export class DoctorService {
6
+ adapters = createChannelAdapters();
7
+ async run(manifest, lockFile) {
8
+ const issues = [];
9
+ for (const source of manifest.sources) {
10
+ const binding = manifest.bindings[source.id] ?? { targets: {} };
11
+ for (const adapter of this.adapters) {
12
+ const configured = binding.targets[adapter.target];
13
+ if (!configured?.enabled) {
14
+ continue;
15
+ }
16
+ const detection = await adapter.detect();
17
+ if (!detection.available) {
18
+ issues.push({
19
+ severity: "error",
20
+ sourceId: source.id,
21
+ target: adapter.target,
22
+ code: "TARGET_UNAVAILABLE",
23
+ message: detection.reason ?? "Target is unavailable.",
24
+ });
25
+ continue;
26
+ }
27
+ for (const leafId of configured.leafIds) {
28
+ const leaf = lockFile.leafInventory.find((item) => item.id === leafId);
29
+ const deployment = lockFile.deployments.find((item) => item.sourceId === source.id &&
30
+ item.leafId === leafId &&
31
+ item.target === adapter.target);
32
+ if (!leaf) {
33
+ issues.push({
34
+ severity: "error",
35
+ sourceId: source.id,
36
+ target: adapter.target,
37
+ leafId,
38
+ code: "LEAF_MISSING",
39
+ message: "This saved selection no longer exists in the source inventory.",
40
+ });
41
+ continue;
42
+ }
43
+ const targetPath = deployment?.targetPath ?? adapter.resolveTargetPath(detection.rootPath, leaf.linkName);
44
+ if (!deployment) {
45
+ issues.push({
46
+ severity: "warning",
47
+ sourceId: source.id,
48
+ target: adapter.target,
49
+ leafId,
50
+ code: "DRIFT_NOT_DEPLOYED",
51
+ message: "This selected skill is not currently projected to disk.",
52
+ });
53
+ continue;
54
+ }
55
+ if (!(await pathExists(targetPath))) {
56
+ issues.push({
57
+ severity: "error",
58
+ sourceId: source.id,
59
+ target: adapter.target,
60
+ leafId,
61
+ code: "TARGET_MISSING",
62
+ message: "Projected target is missing on disk.",
63
+ });
64
+ continue;
65
+ }
66
+ if (deployment.strategy === "symlink") {
67
+ const stats = await fs.lstat(targetPath);
68
+ if (!stats.isSymbolicLink()) {
69
+ issues.push({
70
+ severity: "warning",
71
+ sourceId: source.id,
72
+ target: adapter.target,
73
+ leafId,
74
+ code: "DRIFT_TYPE",
75
+ message: "Expected a symlink, but found foreign content.",
76
+ });
77
+ continue;
78
+ }
79
+ if (await isBrokenSymlink(targetPath)) {
80
+ issues.push({
81
+ severity: "error",
82
+ sourceId: source.id,
83
+ target: adapter.target,
84
+ leafId,
85
+ code: "BROKEN_SYMLINK",
86
+ message: "Projected symlink is broken.",
87
+ });
88
+ }
89
+ }
90
+ else {
91
+ const onDiskHash = await hashDirectory(targetPath);
92
+ if (onDiskHash !== deployment.contentHash) {
93
+ issues.push({
94
+ severity: "warning",
95
+ sourceId: source.id,
96
+ target: adapter.target,
97
+ leafId,
98
+ code: "DRIFT_COPY",
99
+ message: "Projected copy no longer matches saved state.",
100
+ });
101
+ }
102
+ }
103
+ }
104
+ }
105
+ }
106
+ for (const deployment of lockFile.deployments) {
107
+ const sourceStillExists = manifest.sources.some((source) => source.id === deployment.sourceId);
108
+ if (!sourceStillExists) {
109
+ issues.push({
110
+ severity: "warning",
111
+ sourceId: deployment.sourceId,
112
+ target: deployment.target,
113
+ leafId: deployment.leafId,
114
+ code: "STALE_DEPLOYMENT",
115
+ message: "Saved deployment exists for a workflow group that is no longer registered.",
116
+ });
117
+ }
118
+ }
119
+ const hasError = issues.some((issue) => issue.severity === "error");
120
+ const hasWarning = issues.some((issue) => issue.severity === "warning");
121
+ const status = hasError
122
+ ? "BLOCKED"
123
+ : hasWarning
124
+ ? "PARTIAL"
125
+ : "HEALTHY";
126
+ return ok({ status, issues });
127
+ }
128
+ }
129
+ //# sourceMappingURL=doctor-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-service.js","sourceRoot":"","sources":["../../src/services/doctor-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { InvalidLeafRecord, LeafRecord } from "../domain/types.js";
2
+ type InventoryScan = {
3
+ leafs: LeafRecord[];
4
+ invalidLeafs: InvalidLeafRecord[];
5
+ };
6
+ export declare class InventoryService {
7
+ private static readonly IGNORED_DIRECTORIES;
8
+ scanSource(sourceId: string, checkoutPath: string): Promise<InventoryScan>;
9
+ private findSkillFiles;
10
+ private parseSkillFile;
11
+ private dedupeCandidates;
12
+ private parseFrontmatter;
13
+ }
14
+ export {};