@powerhousedao/ph-clint-cli 0.1.0-dev.35 → 0.1.0-dev.37

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.
@@ -13,14 +13,12 @@
13
13
  */
14
14
  import path from 'node:path';
15
15
  import { defineCommand } from '@powerhousedao/ph-clint';
16
- import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
17
16
  import { z } from 'zod';
18
17
  import { clintProjectSpecSchema, } from '../spec/types.js';
19
18
  import { generateProject } from '../codegen/index.js';
20
19
  import { isDirEmptyEnough } from '../codegen/write.js';
21
20
  import { runPostGenActions } from '../codegen/actions.js';
22
- import { writeProjectSpec } from '../spec/file.js';
23
- import { specToImportInput } from '../triggers/spec-change.js';
21
+ import { ensureSpecDocument } from '../spec/ensure-document.js';
24
22
  const inputSchema = z.object({
25
23
  dir: z
26
24
  .string()
@@ -127,14 +125,9 @@ export const init = defineCommand({
127
125
  try {
128
126
  const reactor = await getReactor();
129
127
  if (reactor) {
130
- const newDoc = await reactor.client.createEmpty('powerhouse/ph-clint-project');
131
- const docId = newDoc.header.id;
132
- await folders.addDocument(docId, `specs/${spec.name}`, spec.name);
133
- const importInput = specToImportInput(spec);
134
- await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
135
- spec.documentId = docId;
136
- spec.documentType = 'powerhouse/ph-clint-project';
137
- await writeProjectSpec(targetDir, spec);
128
+ const { docId } = await ensureSpecDocument({
129
+ spec, targetDir, reactor, folders,
130
+ });
138
131
  stdout(`Created spec document ${docId} in Clint Folders/specs/${spec.name}\n`);
139
132
  }
140
133
  }
@@ -12,6 +12,7 @@ import { z } from 'zod';
12
12
  import { readProjectSpec } from '../spec/file.js';
13
13
  import { generateProject } from '../codegen/index.js';
14
14
  import { runPostGenActions } from '../codegen/actions.js';
15
+ import { ensureSpecDocument } from '../spec/ensure-document.js';
15
16
  const inputSchema = z.object({
16
17
  dir: z
17
18
  .string()
@@ -26,7 +27,7 @@ export const regen = defineCommand({
26
27
  id: 'clint-project-regen',
27
28
  description: 'Regenerate project files from the persisted spec',
28
29
  inputSchema,
29
- execute: async (input, { workdir, stdout, log, runProcess }) => {
30
+ execute: async (input, { workdir, stdout, log, runProcess, reactor: getReactor, folders }) => {
30
31
  const targetDir = path.resolve(workdir, input.dir ?? '.');
31
32
  const spec = await readProjectSpec(targetDir);
32
33
  if (!spec) {
@@ -53,6 +54,23 @@ export const regen = defineCommand({
53
54
  else
54
55
  stdout('warning: ' + w + '\n');
55
56
  }
57
+ // Ensure spec document exists in drive (recover if deleted)
58
+ if (getReactor && folders) {
59
+ try {
60
+ const reactor = await getReactor();
61
+ if (reactor) {
62
+ const { docId, created } = await ensureSpecDocument({
63
+ spec, targetDir, reactor, folders,
64
+ });
65
+ if (created) {
66
+ lines.push(`Created spec document ${docId} in Clint Folders/specs/${spec.name}`);
67
+ }
68
+ }
69
+ }
70
+ catch (err) {
71
+ log?.warn(`Could not ensure spec document: ${err instanceof Error ? err.message : String(err)}`);
72
+ }
73
+ }
56
74
  stdout(lines.join('\n') + '\n');
57
75
  // Run post-generation actions (install, build, skills-sync).
58
76
  await runPostGenActions(result.pendingActions, {
@@ -0,0 +1,17 @@
1
+ import type { FolderOperations } from '@powerhousedao/ph-clint';
2
+ import type { ReactorContext, DocumentRegistry } from '@powerhousedao/ph-clint';
3
+ import type { ClintProjectSpec } from './types.js';
4
+ export interface EnsureSpecDocumentResult {
5
+ docId: string;
6
+ created: boolean;
7
+ }
8
+ /**
9
+ * Ensure the spec document exists in the personal drive. Creates one if
10
+ * missing or if the existing `documentId` no longer resolves to a drive node.
11
+ */
12
+ export declare function ensureSpecDocument<R extends DocumentRegistry = DocumentRegistry>(opts: {
13
+ spec: ClintProjectSpec;
14
+ targetDir: string;
15
+ reactor: ReactorContext<R>;
16
+ folders: FolderOperations;
17
+ }): Promise<EnsureSpecDocumentResult>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Ensure a ph-clint-project spec document exists in the personal drive.
3
+ *
4
+ * Single code path used by:
5
+ * - `clint-project-init` (after scaffolding)
6
+ * - `clint-project-regen` (recover deleted docs)
7
+ * - `spec-change` trigger (auto-link unlinked projects)
8
+ *
9
+ * If the spec already has a `documentId` that exists in the drive, this is
10
+ * a no-op. If the document was deleted (or never created), a new one is
11
+ * created, added to the drive at `specs/{name}`, and the spec JSON on disk
12
+ * is updated with the new `documentId`.
13
+ */
14
+ import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
15
+ import { writeProjectSpec } from './file.js';
16
+ import { specToImportInput } from '../triggers/spec-change.js';
17
+ const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
18
+ /**
19
+ * Ensure the spec document exists in the personal drive. Creates one if
20
+ * missing or if the existing `documentId` no longer resolves to a drive node.
21
+ */
22
+ export async function ensureSpecDocument(opts) {
23
+ const { spec, targetDir, reactor, folders } = opts;
24
+ // Check if the document already exists in the drive's folder tree
25
+ if (spec.documentId) {
26
+ const entries = await folders.findByType(DOCUMENT_TYPE);
27
+ const inDrive = entries.some((e) => e.id === spec.documentId);
28
+ if (inDrive)
29
+ return { docId: spec.documentId, created: false };
30
+ }
31
+ // Reuse existing document if it's still in the reactor, otherwise create new
32
+ let docId;
33
+ if (spec.documentId) {
34
+ try {
35
+ const existing = await reactor.client.get(spec.documentId);
36
+ if (existing) {
37
+ docId = spec.documentId;
38
+ }
39
+ else {
40
+ const newDoc = await reactor.client.createEmpty(DOCUMENT_TYPE);
41
+ docId = newDoc.header.id;
42
+ }
43
+ }
44
+ catch {
45
+ const newDoc = await reactor.client.createEmpty(DOCUMENT_TYPE);
46
+ docId = newDoc.header.id;
47
+ }
48
+ }
49
+ else {
50
+ const newDoc = await reactor.client.createEmpty(DOCUMENT_TYPE);
51
+ docId = newDoc.header.id;
52
+ }
53
+ // Add to the drive's folder tree
54
+ await folders.addDocument(docId, `specs/${spec.name}`, spec.name);
55
+ const importInput = specToImportInput(spec);
56
+ await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
57
+ // Persist the link back to disk
58
+ spec.documentId = docId;
59
+ spec.documentType = DOCUMENT_TYPE;
60
+ await writeProjectSpec(targetDir, spec);
61
+ return { docId, created: true };
62
+ }
@@ -17,12 +17,12 @@ import crypto from 'node:crypto';
17
17
  import fs from 'node:fs/promises';
18
18
  import path from 'node:path';
19
19
  import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
20
- import { importSpec } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
21
20
  import { createDocumentChangeTrigger } from '../framework.js';
22
21
  import { generateProject } from '../codegen/index.js';
23
22
  import { runPostGenActions } from '../codegen/actions.js';
24
23
  import { specFromDocumentState } from '../spec/from-document.js';
25
24
  import { readProjectSpec, writeProjectSpec } from '../spec/file.js';
25
+ import { ensureSpecDocument } from '../spec/ensure-document.js';
26
26
  import { clintProject } from '../services/clint-project.js';
27
27
  const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
28
28
  const HASH_DIR = path.join('.ph', 'ph-clint-cli');
@@ -121,24 +121,28 @@ export const specChangeTrigger = createDocumentChangeTrigger({
121
121
  ? scanProjects(ctx.context.workdir, scanner)
122
122
  : [];
123
123
  const mapping = await getProjectMapping(scanResults, folders);
124
- // Auto-create spec documents for unlinked projects
124
+ // Auto-create spec documents for unlinked or orphaned projects
125
125
  const reactor = await ctx.reactor();
126
126
  if (reactor && folders) {
127
127
  for (const entry of mapping) {
128
- if (entry.path && !entry.documentId) {
129
- const existingSpec = await readProjectSpec(entry.path);
130
- if (!existingSpec)
131
- continue;
132
- log?.info(`${TAG} creating spec document for unlinked project ${entry.name}`);
133
- const newDoc = await reactor.client.createEmpty('powerhouse/ph-clint-project');
134
- const docId = newDoc.header.id;
135
- await folders.addDocument(docId, `specs/${entry.name}`, entry.name);
136
- const importInput = specToImportInput(existingSpec);
137
- await reactor.client.execute(docId, 'main', [importSpec(importInput)]);
138
- existingSpec.documentId = docId;
139
- existingSpec.documentType = 'powerhouse/ph-clint-project';
140
- await writeProjectSpec(entry.path, existingSpec);
141
- log?.info(`${TAG} linked project ${entry.name} to document ${docId}`);
128
+ if (!entry.path)
129
+ continue;
130
+ const existingSpec = await readProjectSpec(entry.path);
131
+ if (!existingSpec)
132
+ continue;
133
+ try {
134
+ const { docId, created } = await ensureSpecDocument({
135
+ spec: existingSpec,
136
+ targetDir: entry.path,
137
+ reactor,
138
+ folders,
139
+ });
140
+ if (created) {
141
+ log?.info(`${TAG} linked project ${entry.name} to document ${docId}`);
142
+ }
143
+ }
144
+ catch (err) {
145
+ log?.warn(`${TAG} could not ensure spec document for ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
142
146
  }
143
147
  }
144
148
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/ph-clint-cli",
3
- "version": "0.1.0-dev.35",
3
+ "version": "0.1.0-dev.37",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -29,9 +29,9 @@
29
29
  "@mastra/core": "^1.22.0",
30
30
  "@mastra/libsql": "^1.7.4",
31
31
  "@mastra/memory": "^1.13.1",
32
- "@powerhousedao/ph-clint": "^0.1.0-dev.35",
33
- "@powerhousedao/ph-clint-app": "^0.1.0-dev.35",
34
- "@powerhousedao/ph-clint-dev": "^0.1.0-dev.35",
32
+ "@powerhousedao/ph-clint": "^0.1.0-dev.37",
33
+ "@powerhousedao/ph-clint-app": "^0.1.0-dev.37",
34
+ "@powerhousedao/ph-clint-dev": "^0.1.0-dev.37",
35
35
  "@powerhousedao/reactor": "6.0.0-dev.170",
36
36
  "@powerhousedao/reactor-api": "6.0.0-dev.170",
37
37
  "@powerhousedao/shared": "6.0.0-dev.170",