@workflow-cannon/workspace-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ export declare const defaultWorkspaceKitPaths: {
3
+ readonly profile: "workspace-kit.profile.json";
4
+ readonly profileSchema: "schemas/workspace-kit-profile.schema.json";
5
+ readonly manifest: ".workspace-kit/manifest.json";
6
+ readonly ownedPaths: ".workspace-kit/owned-paths.json";
7
+ };
8
+ export type WorkspaceKitCliOptions = {
9
+ cwd?: string;
10
+ writeLine?: (message: string) => void;
11
+ writeError?: (message: string) => void;
12
+ };
13
+ export declare function parseJsonFile(filePath: string): Promise<unknown>;
14
+ export declare function runCli(args: string[], options?: WorkspaceKitCliOptions): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,580 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+ const EXIT_SUCCESS = 0;
6
+ const EXIT_VALIDATION_FAILURE = 1;
7
+ const EXIT_USAGE_ERROR = 2;
8
+ const EXIT_INTERNAL_ERROR = 3;
9
+ export const defaultWorkspaceKitPaths = {
10
+ profile: "workspace-kit.profile.json",
11
+ profileSchema: "schemas/workspace-kit-profile.schema.json",
12
+ manifest: ".workspace-kit/manifest.json",
13
+ ownedPaths: ".workspace-kit/owned-paths.json"
14
+ };
15
+ const allowedPackageManagers = new Set(["pnpm", "npm", "yarn"]);
16
+ const currentOwnedPaths = [
17
+ "workspace-kit.profile.json",
18
+ "schemas/workspace-kit-profile.schema.json",
19
+ ".workspace-kit/manifest.json",
20
+ ".workspace-kit/owned-paths.json",
21
+ ".cursor/rules/workspace-kit-profile-pointer.mdc",
22
+ ".workspace-kit/generated/project-context.json",
23
+ ".cursor/rules/workspace-kit-project-context.mdc"
24
+ ];
25
+ const pointerRuleContent = `# Workspace Kit Profile Pointer
26
+
27
+ Project-specific identity/config values should come from \`workspace-kit.profile.json\` and generated artifacts under \`.workspace-kit/generated/\`.
28
+
29
+ Do not hardcode project names in rules. Run \`workspace-kit init\` after profile edits to regenerate project-context snippets.
30
+ `;
31
+ const profileSchemaContent = {
32
+ $schema: "https://json-schema.org/draft/2020-12/schema",
33
+ $id: "https://example.com/schemas/workspace-kit-profile.schema.json",
34
+ title: "Workspace Kit Profile",
35
+ type: "object",
36
+ required: ["project", "packageManager", "commands", "github"],
37
+ properties: {
38
+ project: {
39
+ type: "object",
40
+ required: ["name"],
41
+ properties: {
42
+ name: {
43
+ type: "string",
44
+ minLength: 1
45
+ }
46
+ },
47
+ additionalProperties: true
48
+ },
49
+ packageManager: {
50
+ type: "string",
51
+ enum: ["pnpm", "npm", "yarn"]
52
+ },
53
+ commands: {
54
+ type: "object",
55
+ required: ["test", "lint", "typecheck"],
56
+ properties: {
57
+ test: {
58
+ type: "string",
59
+ minLength: 1
60
+ },
61
+ lint: {
62
+ type: "string",
63
+ minLength: 1
64
+ },
65
+ typecheck: {
66
+ type: "string",
67
+ minLength: 1
68
+ }
69
+ },
70
+ additionalProperties: true
71
+ },
72
+ github: {
73
+ type: "object",
74
+ required: ["defaultBranch"],
75
+ properties: {
76
+ defaultBranch: {
77
+ type: "string",
78
+ minLength: 1
79
+ }
80
+ },
81
+ additionalProperties: true
82
+ }
83
+ },
84
+ additionalProperties: true
85
+ };
86
+ export async function parseJsonFile(filePath) {
87
+ const raw = await fs.readFile(filePath, "utf8");
88
+ return JSON.parse(raw);
89
+ }
90
+ function readStringField(objectValue, key, errors, fieldPath) {
91
+ const value = objectValue[key];
92
+ if (typeof value !== "string" || value.trim().length === 0) {
93
+ errors.push(`${fieldPath} must be a non-empty string`);
94
+ return undefined;
95
+ }
96
+ return value;
97
+ }
98
+ async function validateProfile(cwd) {
99
+ const profilePath = path.join(cwd, defaultWorkspaceKitPaths.profile);
100
+ const errors = [];
101
+ let profile;
102
+ try {
103
+ profile = await parseJsonFile(profilePath);
104
+ }
105
+ catch {
106
+ return { errors: [`${defaultWorkspaceKitPaths.profile} is missing or invalid JSON`] };
107
+ }
108
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
109
+ return { errors: ["workspace-kit profile root must be an object"] };
110
+ }
111
+ const profileObject = profile;
112
+ const project = profileObject.project;
113
+ const commands = profileObject.commands;
114
+ const github = profileObject.github;
115
+ const packageManager = profileObject.packageManager;
116
+ if (!project || typeof project !== "object" || Array.isArray(project)) {
117
+ errors.push("project must be an object");
118
+ }
119
+ else {
120
+ readStringField(project, "name", errors, "project.name");
121
+ }
122
+ if (typeof packageManager !== "string" || !allowedPackageManagers.has(packageManager)) {
123
+ errors.push("packageManager must be one of: pnpm, npm, yarn");
124
+ }
125
+ if (!commands || typeof commands !== "object" || Array.isArray(commands)) {
126
+ errors.push("commands must be an object");
127
+ }
128
+ else {
129
+ const commandsObject = commands;
130
+ readStringField(commandsObject, "test", errors, "commands.test");
131
+ readStringField(commandsObject, "lint", errors, "commands.lint");
132
+ readStringField(commandsObject, "typecheck", errors, "commands.typecheck");
133
+ }
134
+ if (!github || typeof github !== "object" || Array.isArray(github)) {
135
+ errors.push("github must be an object");
136
+ }
137
+ else {
138
+ readStringField(github, "defaultBranch", errors, "github.defaultBranch");
139
+ }
140
+ if (errors.length > 0) {
141
+ return { errors };
142
+ }
143
+ const validatedProject = profileObject.project;
144
+ const validatedCommands = profileObject.commands;
145
+ const validatedGithub = profileObject.github;
146
+ const validatedPackageManager = profileObject.packageManager;
147
+ return {
148
+ errors,
149
+ profile: {
150
+ project: { name: validatedProject.name },
151
+ packageManager: validatedPackageManager,
152
+ commands: {
153
+ test: validatedCommands.test,
154
+ lint: validatedCommands.lint,
155
+ typecheck: validatedCommands.typecheck
156
+ },
157
+ github: {
158
+ defaultBranch: validatedGithub.defaultBranch
159
+ }
160
+ }
161
+ };
162
+ }
163
+ async function generateProfileDrivenArtifacts(cwd, profile) {
164
+ const generatedJsonPath = path.join(cwd, ".workspace-kit", "generated", "project-context.json");
165
+ const generatedRulePath = path.join(cwd, ".cursor", "rules", "workspace-kit-project-context.mdc");
166
+ const renderedArtifacts = renderProfileDrivenArtifacts(profile);
167
+ await fs.mkdir(path.dirname(generatedJsonPath), { recursive: true });
168
+ await fs.writeFile(generatedJsonPath, renderedArtifacts.generatedContextJson, "utf8");
169
+ await fs.mkdir(path.dirname(generatedRulePath), { recursive: true });
170
+ await fs.writeFile(generatedRulePath, renderedArtifacts.generatedRuleText, "utf8");
171
+ return { generatedJsonPath, generatedRulePath };
172
+ }
173
+ function toJsonWithTrailingNewline(value) {
174
+ return `${JSON.stringify(value, null, 2)}\n`;
175
+ }
176
+ function renderProfileDrivenArtifacts(profile) {
177
+ const generatedContext = {
178
+ generatedFrom: defaultWorkspaceKitPaths.profile,
179
+ projectName: profile.project.name,
180
+ packageManager: profile.packageManager,
181
+ commands: profile.commands,
182
+ github: profile.github
183
+ };
184
+ const generatedRuleText = [
185
+ "# Workspace Kit Project Context (Generated)",
186
+ "",
187
+ "This file is generated by `workspace-kit init`.",
188
+ "Do not hand-edit project identity values here; update `workspace-kit.profile.json` and rerun init.",
189
+ "",
190
+ `- project_name: ${profile.project.name}`,
191
+ `- package_manager: ${profile.packageManager}`,
192
+ `- default_branch: ${profile.github.defaultBranch}`,
193
+ `- test_command: ${profile.commands.test}`,
194
+ `- lint_command: ${profile.commands.lint}`,
195
+ `- typecheck_command: ${profile.commands.typecheck}`,
196
+ "",
197
+ "When instructions need project-specific values, prefer this generated file or `.workspace-kit/generated/project-context.json`."
198
+ ].join("\n");
199
+ return {
200
+ generatedContextJson: toJsonWithTrailingNewline(generatedContext),
201
+ generatedRuleText: `${generatedRuleText}\n`
202
+ };
203
+ }
204
+ function createDriftExpectedAssets(profile) {
205
+ const generated = renderProfileDrivenArtifacts(profile);
206
+ return new Map([
207
+ [defaultWorkspaceKitPaths.profileSchema, toJsonWithTrailingNewline(profileSchemaContent)],
208
+ [".cursor/rules/workspace-kit-profile-pointer.mdc", pointerRuleContent],
209
+ [".workspace-kit/generated/project-context.json", generated.generatedContextJson],
210
+ [".cursor/rules/workspace-kit-project-context.mdc", generated.generatedRuleText]
211
+ ]);
212
+ }
213
+ function driftContentMatches(relativePath, existingContent, expectedContent) {
214
+ if (!relativePath.endsWith(".json")) {
215
+ return existingContent === expectedContent;
216
+ }
217
+ try {
218
+ const existingJson = JSON.parse(existingContent);
219
+ const expectedJson = JSON.parse(expectedContent);
220
+ return JSON.stringify(existingJson) === JSON.stringify(expectedJson);
221
+ }
222
+ catch {
223
+ return existingContent === expectedContent;
224
+ }
225
+ }
226
+ async function resolvePackageVersion(cwd) {
227
+ const candidatePaths = [
228
+ path.join(cwd, "packages", "workspace-kit", "package.json"),
229
+ path.join(cwd, "package.json")
230
+ ];
231
+ for (const candidatePath of candidatePaths) {
232
+ try {
233
+ const parsed = await parseJsonFile(candidatePath);
234
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
235
+ const version = parsed.version;
236
+ const name = parsed.name;
237
+ if (typeof version === "string" && version.length > 0 && typeof name === "string") {
238
+ if (name === "quicktask-workspace-kit" ||
239
+ candidatePath.endsWith("packages/workspace-kit/package.json")) {
240
+ return version;
241
+ }
242
+ }
243
+ }
244
+ }
245
+ catch {
246
+ continue;
247
+ }
248
+ }
249
+ return undefined;
250
+ }
251
+ async function readOwnedPathsDocument(cwd) {
252
+ const ownedPathsPath = path.join(cwd, defaultWorkspaceKitPaths.ownedPaths);
253
+ let parsed;
254
+ try {
255
+ parsed = await parseJsonFile(ownedPathsPath);
256
+ }
257
+ catch {
258
+ return {
259
+ schemaVersion: 1,
260
+ ownedPaths: currentOwnedPaths,
261
+ notes: "Owned path policy fallback generated by workspace-kit upgrade."
262
+ };
263
+ }
264
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
265
+ return {
266
+ schemaVersion: 1,
267
+ ownedPaths: currentOwnedPaths,
268
+ notes: "Owned path policy fallback generated by workspace-kit upgrade."
269
+ };
270
+ }
271
+ const value = parsed;
272
+ const ownedPaths = Array.isArray(value.ownedPaths)
273
+ ? value.ownedPaths.filter((item) => typeof item === "string")
274
+ : [];
275
+ return {
276
+ schemaVersion: typeof value.schemaVersion === "number" ? value.schemaVersion : 1,
277
+ ownedPaths: ownedPaths.length > 0 ? ownedPaths : currentOwnedPaths,
278
+ notes: typeof value.notes === "string" ? value.notes : undefined
279
+ };
280
+ }
281
+ async function writeFileWithBackupIfChanged(cwd, relativePath, content, backupRoot) {
282
+ const targetPath = path.join(cwd, relativePath);
283
+ let existingContent;
284
+ try {
285
+ existingContent = await fs.readFile(targetPath, "utf8");
286
+ }
287
+ catch {
288
+ existingContent = undefined;
289
+ }
290
+ if (existingContent === content) {
291
+ return false;
292
+ }
293
+ if (typeof existingContent === "string") {
294
+ const backupPath = path.join(backupRoot, `${relativePath}.bak`);
295
+ await fs.mkdir(path.dirname(backupPath), { recursive: true });
296
+ await fs.writeFile(backupPath, existingContent, "utf8");
297
+ }
298
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
299
+ await fs.writeFile(targetPath, content, "utf8");
300
+ return true;
301
+ }
302
+ export async function runCli(args, options = {}) {
303
+ const cwd = options.cwd ?? process.cwd();
304
+ const writeLine = options.writeLine ?? console.log;
305
+ const writeError = options.writeError ?? console.error;
306
+ const [command] = args;
307
+ if (!command) {
308
+ writeError("Usage: workspace-kit <init|doctor|check|upgrade>");
309
+ return EXIT_USAGE_ERROR;
310
+ }
311
+ if (command === "init") {
312
+ const { errors, profile } = await validateProfile(cwd);
313
+ if (errors.length > 0 || !profile) {
314
+ writeError("workspace-kit init failed profile validation.");
315
+ for (const error of errors) {
316
+ writeError(`- ${error}`);
317
+ }
318
+ return EXIT_VALIDATION_FAILURE;
319
+ }
320
+ const artifacts = await generateProfileDrivenArtifacts(cwd, profile);
321
+ writeLine("workspace-kit init generated profile-driven project context artifacts.");
322
+ writeLine(`- ${path.relative(cwd, artifacts.generatedJsonPath)}`);
323
+ writeLine(`- ${path.relative(cwd, artifacts.generatedRulePath)}`);
324
+ return EXIT_SUCCESS;
325
+ }
326
+ if (command === "check") {
327
+ const { errors } = await validateProfile(cwd);
328
+ if (errors.length > 0) {
329
+ writeError("workspace-kit check failed profile validation.");
330
+ for (const error of errors) {
331
+ writeError(`- ${error}`);
332
+ }
333
+ return EXIT_VALIDATION_FAILURE;
334
+ }
335
+ writeLine("workspace-kit check passed.");
336
+ writeLine("Profile validation succeeded for required baseline fields.");
337
+ return EXIT_SUCCESS;
338
+ }
339
+ if (command === "upgrade") {
340
+ const { errors, profile } = await validateProfile(cwd);
341
+ if (errors.length > 0 || !profile) {
342
+ writeError("workspace-kit upgrade failed profile validation.");
343
+ for (const error of errors) {
344
+ writeError(`- ${error}`);
345
+ }
346
+ return EXIT_VALIDATION_FAILURE;
347
+ }
348
+ const ownedPathsDocument = await readOwnedPathsDocument(cwd);
349
+ const backupRoot = path.join(cwd, ".workspace-kit", "backups", new Date().toISOString());
350
+ const updatedPaths = [];
351
+ const preservedPaths = [];
352
+ const unsupportedPaths = [];
353
+ const ownedPathSet = new Set(ownedPathsDocument.ownedPaths);
354
+ ownedPathSet.add(defaultWorkspaceKitPaths.profile);
355
+ ownedPathSet.add(defaultWorkspaceKitPaths.profileSchema);
356
+ ownedPathSet.add(defaultWorkspaceKitPaths.manifest);
357
+ ownedPathSet.add(defaultWorkspaceKitPaths.ownedPaths);
358
+ ownedPathSet.add(".cursor/rules/workspace-kit-profile-pointer.mdc");
359
+ const existingManifestValue = await parseJsonFile(path.join(cwd, defaultWorkspaceKitPaths.manifest)).catch(() => ({}));
360
+ const existingManifest = existingManifestValue &&
361
+ typeof existingManifestValue === "object" &&
362
+ !Array.isArray(existingManifestValue)
363
+ ? existingManifestValue
364
+ : {};
365
+ const nowIso = new Date().toISOString();
366
+ const mergedManifest = {
367
+ schemaVersion: 1,
368
+ kit: {
369
+ name: typeof existingManifest.kit === "object" &&
370
+ existingManifest.kit &&
371
+ typeof existingManifest.kit.name === "string"
372
+ ? existingManifest.kit.name
373
+ : "quicktask-workspace-kit",
374
+ version: typeof existingManifest.kit === "object" &&
375
+ existingManifest.kit &&
376
+ typeof existingManifest.kit.version === "string"
377
+ ? existingManifest.kit.version
378
+ : "0.0.0"
379
+ },
380
+ installedAt: typeof existingManifest.installedAt === "string" && existingManifest.installedAt.length > 0
381
+ ? existingManifest.installedAt
382
+ : nowIso,
383
+ lastUpgrade: nowIso,
384
+ ownershipPolicyPath: defaultWorkspaceKitPaths.ownedPaths
385
+ };
386
+ const desiredAssets = new Map([
387
+ [defaultWorkspaceKitPaths.profileSchema, toJsonWithTrailingNewline(profileSchemaContent)],
388
+ [defaultWorkspaceKitPaths.manifest, toJsonWithTrailingNewline(mergedManifest)],
389
+ [
390
+ defaultWorkspaceKitPaths.ownedPaths,
391
+ toJsonWithTrailingNewline({
392
+ schemaVersion: 1,
393
+ ownedPaths: currentOwnedPaths,
394
+ notes: "Phase 3 baseline: managed kit-owned paths are updated by workspace-kit upgrade with backup safety."
395
+ })
396
+ ],
397
+ [".cursor/rules/workspace-kit-profile-pointer.mdc", `${pointerRuleContent}`]
398
+ ]);
399
+ for (const ownedPath of ownedPathSet) {
400
+ if (ownedPath === defaultWorkspaceKitPaths.profile) {
401
+ preservedPaths.push(ownedPath);
402
+ continue;
403
+ }
404
+ const desiredContent = desiredAssets.get(ownedPath);
405
+ if (!desiredContent) {
406
+ if (ownedPath !== ".workspace-kit/generated/project-context.json" &&
407
+ ownedPath !== ".cursor/rules/workspace-kit-project-context.mdc") {
408
+ unsupportedPaths.push(ownedPath);
409
+ }
410
+ continue;
411
+ }
412
+ const changed = await writeFileWithBackupIfChanged(cwd, ownedPath, desiredContent, backupRoot);
413
+ if (changed) {
414
+ updatedPaths.push(ownedPath);
415
+ }
416
+ }
417
+ const generatedArtifacts = await generateProfileDrivenArtifacts(cwd, profile);
418
+ updatedPaths.push(path.relative(cwd, generatedArtifacts.generatedJsonPath));
419
+ updatedPaths.push(path.relative(cwd, generatedArtifacts.generatedRulePath));
420
+ writeLine("workspace-kit upgrade completed.");
421
+ if (updatedPaths.length > 0) {
422
+ writeLine("Updated kit-owned paths:");
423
+ for (const updatedPath of updatedPaths) {
424
+ writeLine(`- ${updatedPath}`);
425
+ }
426
+ }
427
+ if (preservedPaths.length > 0) {
428
+ writeLine("Preserved merge-managed paths:");
429
+ for (const preservedPath of preservedPaths) {
430
+ writeLine(`- ${preservedPath}`);
431
+ }
432
+ }
433
+ if (unsupportedPaths.length > 0) {
434
+ writeLine("Ignored unsupported owned paths:");
435
+ for (const unsupportedPath of unsupportedPaths) {
436
+ writeLine(`- ${unsupportedPath}`);
437
+ }
438
+ }
439
+ writeLine(`Backups written under: ${path.relative(cwd, backupRoot)}`);
440
+ return EXIT_SUCCESS;
441
+ }
442
+ if (command === "drift-check") {
443
+ const { errors, profile } = await validateProfile(cwd);
444
+ if (errors.length > 0 || !profile) {
445
+ writeError("workspace-kit drift-check failed profile validation.");
446
+ for (const error of errors) {
447
+ writeError(`- ${error}`);
448
+ }
449
+ return EXIT_VALIDATION_FAILURE;
450
+ }
451
+ const driftFindings = [];
452
+ const warnings = [];
453
+ const expectedAssets = createDriftExpectedAssets(profile);
454
+ const ownedPathsDocument = await readOwnedPathsDocument(cwd);
455
+ const ownedPathSet = new Set(ownedPathsDocument.ownedPaths);
456
+ for (const ownedPath of ownedPathSet) {
457
+ const expectedContent = expectedAssets.get(ownedPath);
458
+ if (!expectedContent) {
459
+ warnings.push(`unsupported owned path skipped: ${ownedPath}`);
460
+ continue;
461
+ }
462
+ const targetPath = path.join(cwd, ownedPath);
463
+ let existingContent;
464
+ try {
465
+ existingContent = await fs.readFile(targetPath, "utf8");
466
+ }
467
+ catch {
468
+ driftFindings.push(`${ownedPath}: missing`);
469
+ continue;
470
+ }
471
+ if (!driftContentMatches(ownedPath, existingContent, expectedContent)) {
472
+ driftFindings.push(`${ownedPath}: content drift detected`);
473
+ }
474
+ }
475
+ const manifestPath = path.join(cwd, defaultWorkspaceKitPaths.manifest);
476
+ let manifest;
477
+ try {
478
+ const parsedManifest = await parseJsonFile(manifestPath);
479
+ if (parsedManifest && typeof parsedManifest === "object" && !Array.isArray(parsedManifest)) {
480
+ manifest = parsedManifest;
481
+ }
482
+ else {
483
+ driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: invalid structure`);
484
+ }
485
+ }
486
+ catch {
487
+ driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: missing or invalid JSON`);
488
+ }
489
+ if (manifest) {
490
+ if (manifest.ownershipPolicyPath !== defaultWorkspaceKitPaths.ownedPaths) {
491
+ driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: ownershipPolicyPath must equal ${defaultWorkspaceKitPaths.ownedPaths}`);
492
+ }
493
+ const manifestKit = manifest.kit && typeof manifest.kit === "object" && !Array.isArray(manifest.kit)
494
+ ? manifest.kit
495
+ : undefined;
496
+ const manifestKitName = manifestKit && typeof manifestKit.name === "string" ? manifestKit.name : undefined;
497
+ const manifestKitVersion = manifestKit && typeof manifestKit.version === "string" ? manifestKit.version : undefined;
498
+ const packageVersion = await resolvePackageVersion(cwd);
499
+ if (!manifestKitName || !manifestKitVersion) {
500
+ driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: kit.name and kit.version are required`);
501
+ }
502
+ else if (manifestKitName === "quicktask-workspace-kit" && packageVersion) {
503
+ if (manifestKitVersion !== packageVersion) {
504
+ driftFindings.push(`${defaultWorkspaceKitPaths.manifest}: kit.version (${manifestKitVersion}) does not match package version (${packageVersion})`);
505
+ }
506
+ }
507
+ else if (manifestKitName !== "quicktask-workspace-kit") {
508
+ warnings.push(`${defaultWorkspaceKitPaths.manifest}: kit.name is '${manifestKitName}', skipping package-version drift comparison`);
509
+ }
510
+ }
511
+ if (driftFindings.length > 0) {
512
+ writeError("workspace-kit drift-check detected drift.");
513
+ for (const finding of driftFindings) {
514
+ writeError(`- ${finding}`);
515
+ }
516
+ for (const warning of warnings) {
517
+ writeError(`- warning: ${warning}`);
518
+ }
519
+ return EXIT_VALIDATION_FAILURE;
520
+ }
521
+ writeLine("workspace-kit drift-check passed.");
522
+ writeLine("No managed asset drift detected for supported owned paths.");
523
+ for (const warning of warnings) {
524
+ writeLine(`- warning: ${warning}`);
525
+ }
526
+ return EXIT_SUCCESS;
527
+ }
528
+ if (command !== "doctor") {
529
+ writeError(`Unknown command '${command}'. Supported commands: init, doctor, check, upgrade, drift-check.`);
530
+ return EXIT_USAGE_ERROR;
531
+ }
532
+ const issues = [];
533
+ const requiredPaths = Object.values(defaultWorkspaceKitPaths).map((relativePath) => path.join(cwd, relativePath));
534
+ for (const requiredPath of requiredPaths) {
535
+ try {
536
+ await fs.access(requiredPath);
537
+ }
538
+ catch {
539
+ issues.push({
540
+ path: path.relative(cwd, requiredPath) || requiredPath,
541
+ reason: "missing"
542
+ });
543
+ continue;
544
+ }
545
+ try {
546
+ await parseJsonFile(requiredPath);
547
+ }
548
+ catch {
549
+ issues.push({
550
+ path: path.relative(cwd, requiredPath) || requiredPath,
551
+ reason: "invalid-json"
552
+ });
553
+ }
554
+ }
555
+ if (issues.length > 0) {
556
+ writeError("workspace-kit doctor failed validation.");
557
+ for (const issue of issues) {
558
+ writeError(`- ${issue.path}: ${issue.reason}`);
559
+ }
560
+ return EXIT_VALIDATION_FAILURE;
561
+ }
562
+ writeLine("workspace-kit doctor passed.");
563
+ writeLine("All canonical workspace-kit contract files are present and parseable JSON.");
564
+ return EXIT_SUCCESS;
565
+ }
566
+ async function main() {
567
+ try {
568
+ const code = await runCli(process.argv.slice(2));
569
+ process.exitCode = code;
570
+ }
571
+ catch (error) {
572
+ const message = error instanceof Error ? error.message : String(error);
573
+ console.error(`workspace-kit internal error: ${message}`);
574
+ process.exitCode = EXIT_INTERNAL_ERROR;
575
+ }
576
+ }
577
+ const isDirectExecution = typeof process.argv[1] === "string" && import.meta.url === pathToFileURL(process.argv[1]).href;
578
+ if (isDirectExecution) {
579
+ void main();
580
+ }
@@ -0,0 +1 @@
1
+ export { defaultWorkspaceKitPaths, parseJsonFile, runCli, type WorkspaceKitCliOptions } from "./cli.js";
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { defaultWorkspaceKitPaths, parseJsonFile, runCli } from "./cli.js";
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@workflow-cannon/workspace-kit",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "packageManager": "pnpm@10.0.0",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/NJLaPrell/workflow-cannon.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "registry": "https://registry.npmjs.org/"
14
+ },
15
+ "type": "module",
16
+ "main": "dist/index.js",
17
+ "bin": {
18
+ "workspace-kit": "dist/cli.js"
19
+ },
20
+ "scripts": {
21
+ "build": "tsc -p tsconfig.json",
22
+ "check": "tsc -p tsconfig.json --noEmit",
23
+ "clean": "rm -rf dist",
24
+ "test": "pnpm run build && node --test test/**/*.test.mjs",
25
+ "pack:dry-run": "pnpm run build && pnpm pack --pack-destination ./artifacts/workspace-kit-pack"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^25.5.0",
29
+ "typescript": "^5.9.3"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "package.json"
34
+ ]
35
+ }