@powerhousedao/ph-clint-cli 0.0.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/README.md +84 -0
- package/dist/agents/clint-agent.d.ts +12 -0
- package/dist/agents/clint-agent.js +42 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +122 -0
- package/dist/codegen/index.d.ts +11 -0
- package/dist/codegen/index.js +11 -0
- package/dist/commands/clint-project-build.d.ts +83 -0
- package/dist/commands/clint-project-build.js +34 -0
- package/dist/commands/clint-project-init.d.ts +177 -0
- package/dist/commands/clint-project-init.js +193 -0
- package/dist/commands/clint-project-publish.d.ts +92 -0
- package/dist/commands/clint-project-publish.js +73 -0
- package/dist/commands/clint-project-regen.d.ts +94 -0
- package/dist/commands/clint-project-regen.js +92 -0
- package/dist/commands/clint-skills-sync.d.ts +86 -0
- package/dist/commands/clint-skills-sync.js +56 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +5 -0
- package/dist/framework.d.ts +501 -0
- package/dist/framework.gen.d.ts +77 -0
- package/dist/framework.gen.js +16 -0
- package/dist/framework.js +37 -0
- package/dist/gen/agent-profiles/PhClintDevAgent.md +13 -0
- package/dist/gen/skills/cli-setup/.cli-docs.md +135 -0
- package/dist/gen/skills/cli-setup/SKILL.md +125 -0
- package/dist/gen/skills/cli-setup/references/00.assess-cli-needs.md +53 -0
- package/dist/gen/skills/cli-setup/references/01.define-config-schema.md +78 -0
- package/dist/gen/skills/cli-setup/references/02.wire-cli.md +141 -0
- package/dist/gen/skills/cli-setup/references/03.add-entrypoint.md +128 -0
- package/dist/gen/skills/cli-setup/references/expected-outcome.md +3 -0
- package/dist/gen/skills/command-definition/.cli-docs.md +84 -0
- package/dist/gen/skills/command-definition/SKILL.md +99 -0
- package/dist/gen/skills/command-definition/references/00.assess-requirements.md +58 -0
- package/dist/gen/skills/command-definition/references/01.define-schema.md +72 -0
- package/dist/gen/skills/command-definition/references/02.implement-execute.md +118 -0
- package/dist/gen/skills/command-definition/references/03.add-prompting.md +54 -0
- package/dist/gen/skills/command-definition/references/expected-outcome.md +3 -0
- package/dist/gen/skills/service-definition/.cli-docs.md +214 -0
- package/dist/gen/skills/service-definition/SKILL.md +148 -0
- package/dist/gen/skills/service-definition/references/00.assess-service.md +69 -0
- package/dist/gen/skills/service-definition/references/01.define-service.md +166 -0
- package/dist/gen/skills/service-definition/references/02.add-preflight.md +132 -0
- package/dist/gen/skills/service-definition/references/03.add-project-scanner.md +82 -0
- package/dist/gen/skills/service-definition/references/04.wire-events.md +120 -0
- package/dist/gen/skills/service-definition/references/expected-outcome.md +3 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +3 -0
- package/dist/mastra/index.d.ts +10 -0
- package/dist/mastra/index.js +11 -0
- package/dist/services/clint-project.d.ts +1 -0
- package/dist/services/clint-project.js +68 -0
- package/dist/spec/ensure-document.d.ts +17 -0
- package/dist/spec/ensure-document.js +56 -0
- package/dist/spec/file.d.ts +1 -0
- package/dist/spec/file.js +1 -0
- package/dist/spec/from-document.d.ts +21 -0
- package/dist/spec/from-document.js +18 -0
- package/dist/spec/types.d.ts +1 -0
- package/dist/spec/types.js +1 -0
- package/dist/triggers/publish-trigger.d.ts +81 -0
- package/dist/triggers/publish-trigger.js +137 -0
- package/dist/triggers/spec-change.d.ts +85 -0
- package/dist/triggers/spec-change.js +224 -0
- package/package.json +54 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a `powerhouse/ph-clint-project` document's global state into a
|
|
3
|
+
* `ClintProjectSpec` that the code generator consumes.
|
|
4
|
+
*
|
|
5
|
+
* The document state is deliberately more permissive than the codegen spec:
|
|
6
|
+
* `name` is nullable in the state (so empty documents are creatable), but the
|
|
7
|
+
* generator requires a valid package name. Callers handle the `null` result
|
|
8
|
+
* (typically by skipping the regen until the user sets a name).
|
|
9
|
+
*/
|
|
10
|
+
import type { PhClintProjectGlobalState } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
11
|
+
import { type ClintProjectSpec } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Build a `ClintProjectSpec` from the document state.
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` if the document has no valid package name yet — callers
|
|
16
|
+
* should wait for the user to populate it before triggering codegen.
|
|
17
|
+
*/
|
|
18
|
+
export declare function specFromDocumentState(state: PhClintProjectGlobalState, meta?: {
|
|
19
|
+
documentId?: string;
|
|
20
|
+
documentType?: string;
|
|
21
|
+
}): ClintProjectSpec | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { clintProjectSpecSchema, } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a `ClintProjectSpec` from the document state.
|
|
4
|
+
*
|
|
5
|
+
* Returns `null` if the document has no valid package name yet — callers
|
|
6
|
+
* should wait for the user to populate it before triggering codegen.
|
|
7
|
+
*/
|
|
8
|
+
export function specFromDocumentState(state, meta) {
|
|
9
|
+
if (!state.name)
|
|
10
|
+
return null;
|
|
11
|
+
const parsed = clintProjectSpecSchema.safeParse({
|
|
12
|
+
...state,
|
|
13
|
+
scope: state.scope ?? undefined,
|
|
14
|
+
documentId: meta?.documentId,
|
|
15
|
+
documentType: meta?.documentType,
|
|
16
|
+
});
|
|
17
|
+
return parsed.success ? parsed.data : null;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { POWERHOUSE_LEVELS, powerhouseLevelSchema, type PowerhouseLevel, phAtLeast, agentModelSchema, type AgentModel, agentProfileSchema, type AgentProfile, powerhousePackageSchema, type PowerhousePackage, externalSkillSchema, type ExternalSkill, clintProjectSpecSchema, type ClintProjectSpec, type ClintProjectSpecInput, getPackageName, getBinName, getCliFolderName, getAppFolderName, getAllDocumentTypes, getAppPackageName, getAppDirName, getDocumentTypeSlug, getDocumentTypeModuleName, isDocTypeGlob, docTypeGlobToRegex, } from '@powerhousedao/ph-clint-dev/spec';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { POWERHOUSE_LEVELS, powerhouseLevelSchema, phAtLeast, agentModelSchema, agentProfileSchema, powerhousePackageSchema, externalSkillSchema, clintProjectSpecSchema, getPackageName, getBinName, getCliFolderName, getAppFolderName, getAllDocumentTypes, getAppPackageName, getAppDirName, getDocumentTypeSlug, getDocumentTypeModuleName, isDocTypeGlob, docTypeGlobToRegex, } from '@powerhousedao/ph-clint-dev/spec';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** Map document model PublishTag values to CLI --tag values. */
|
|
2
|
+
export declare function tagToCli(tag: string): 'dev' | 'staging' | 'production' | null;
|
|
3
|
+
export declare const publishTrigger: import("@powerhousedao/ph-clint").Trigger<{
|
|
4
|
+
pending: number;
|
|
5
|
+
}, {
|
|
6
|
+
model: string;
|
|
7
|
+
devServicePort: number;
|
|
8
|
+
phVersion: string;
|
|
9
|
+
apiKey?: string | undefined;
|
|
10
|
+
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [{
|
|
11
|
+
readonly version: 1;
|
|
12
|
+
readonly reducer: import("document-model").Reducer<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>;
|
|
13
|
+
readonly actions: {
|
|
14
|
+
setAgentModel: (input: import("@powerhousedao/ph-clint-app").SetAgentModelInput) => import("@powerhousedao/ph-clint-app").SetAgentModelAction;
|
|
15
|
+
addAgentProfileRef: (input: import("@powerhousedao/ph-clint-app").AddAgentProfileRefInput) => import("@powerhousedao/ph-clint-app").AddAgentProfileRefAction;
|
|
16
|
+
removeAgentProfileRef: (input: import("@powerhousedao/ph-clint-app").RemoveAgentProfileRefInput) => import("@powerhousedao/ph-clint-app").RemoveAgentProfileRefAction;
|
|
17
|
+
reorderAgentProfileRefs: (input: import("@powerhousedao/ph-clint-app").ReorderAgentProfileRefsInput) => import("@powerhousedao/ph-clint-app").ReorderAgentProfileRefsAction;
|
|
18
|
+
addAgentSkill: (input: import("@powerhousedao/ph-clint-app").AddAgentSkillInput) => import("@powerhousedao/ph-clint-app").AddAgentSkillAction;
|
|
19
|
+
removeAgentSkill: (input: import("@powerhousedao/ph-clint-app").RemoveAgentSkillInput) => import("@powerhousedao/ph-clint-app").RemoveAgentSkillAction;
|
|
20
|
+
addAgentToolPattern: (input: import("@powerhousedao/ph-clint-app").AddAgentToolPatternInput) => import("@powerhousedao/ph-clint-app").AddAgentToolPatternAction;
|
|
21
|
+
removeAgentToolPattern: (input: import("@powerhousedao/ph-clint-app").RemoveAgentToolPatternInput) => import("@powerhousedao/ph-clint-app").RemoveAgentToolPatternAction;
|
|
22
|
+
addSubAgent: (input: import("@powerhousedao/ph-clint-app").AddSubAgentInput) => import("@powerhousedao/ph-clint-app").AddSubAgentAction;
|
|
23
|
+
removeSubAgent: (input: import("@powerhousedao/ph-clint-app").RemoveSubAgentInput) => import("@powerhousedao/ph-clint-app").RemoveSubAgentAction;
|
|
24
|
+
setSubAgentName: (input: import("@powerhousedao/ph-clint-app").SetSubAgentNameInput) => import("@powerhousedao/ph-clint-app").SetSubAgentNameAction;
|
|
25
|
+
setSubAgentDescription: (input: import("@powerhousedao/ph-clint-app").SetSubAgentDescriptionInput) => import("@powerhousedao/ph-clint-app").SetSubAgentDescriptionAction;
|
|
26
|
+
setMainAgentName: (input: import("@powerhousedao/ph-clint-app").SetMainAgentNameInput) => import("@powerhousedao/ph-clint-app").SetMainAgentNameAction;
|
|
27
|
+
setMainAgentDescription: (input: import("@powerhousedao/ph-clint-app").SetMainAgentDescriptionInput) => import("@powerhousedao/ph-clint-app").SetMainAgentDescriptionAction;
|
|
28
|
+
clearMainAgentDescription: (input: import("@powerhousedao/ph-clint-app").ClearMainAgentDescriptionInput) => import("@powerhousedao/ph-clint-app").ClearMainAgentDescriptionAction;
|
|
29
|
+
setMainAgentImage: (input: import("@powerhousedao/ph-clint-app").SetMainAgentImageInput) => import("@powerhousedao/ph-clint-app").SetMainAgentImageAction;
|
|
30
|
+
clearMainAgentImage: (input: import("@powerhousedao/ph-clint-app").ClearMainAgentImageInput) => import("@powerhousedao/ph-clint-app").ClearMainAgentImageAction;
|
|
31
|
+
addProfile: (input: import("@powerhousedao/ph-clint-app").AddProfileInput) => import("@powerhousedao/ph-clint-app").AddProfileAction;
|
|
32
|
+
updateProfile: (input: import("@powerhousedao/ph-clint-app").UpdateProfileInput) => import("@powerhousedao/ph-clint-app").UpdateProfileAction;
|
|
33
|
+
removeProfile: (input: import("@powerhousedao/ph-clint-app").RemoveProfileInput) => import("@powerhousedao/ph-clint-app").RemoveProfileAction;
|
|
34
|
+
reorderProfiles: (input: import("@powerhousedao/ph-clint-app").ReorderProfilesInput) => import("@powerhousedao/ph-clint-app").ReorderProfilesAction;
|
|
35
|
+
addModel: (input: import("@powerhousedao/ph-clint-app").AddModelInput) => import("@powerhousedao/ph-clint-app").AddModelAction;
|
|
36
|
+
removeModel: (input: import("@powerhousedao/ph-clint-app").RemoveModelInput) => import("@powerhousedao/ph-clint-app").RemoveModelAction;
|
|
37
|
+
enableMastra: (input: import("@powerhousedao/ph-clint-app").EnableMastraInput) => import("@powerhousedao/ph-clint-app").EnableMastraAction;
|
|
38
|
+
disableMastra: (input: import("@powerhousedao/ph-clint-app").DisableMastraInput) => import("@powerhousedao/ph-clint-app").DisableMastraAction;
|
|
39
|
+
setEnableChat: (input: import("@powerhousedao/ph-clint-app").SetEnableChatInput) => import("@powerhousedao/ph-clint-app").SetEnableChatAction;
|
|
40
|
+
addSupportedResource: (input: import("@powerhousedao/ph-clint-app").AddSupportedResourceInput) => import("@powerhousedao/ph-clint-app").AddSupportedResourceAction;
|
|
41
|
+
removeSupportedResource: (input: import("@powerhousedao/ph-clint-app").RemoveSupportedResourceInput) => import("@powerhousedao/ph-clint-app").RemoveSupportedResourceAction;
|
|
42
|
+
setProxyEnabled: (input: import("@powerhousedao/ph-clint-app").SetProxyEnabledInput) => import("@powerhousedao/ph-clint-app").SetProxyEnabledAction;
|
|
43
|
+
setObservabilityEnabled: (input: import("@powerhousedao/ph-clint-app").SetObservabilityEnabledInput) => import("@powerhousedao/ph-clint-app").SetObservabilityEnabledAction;
|
|
44
|
+
importSpec: (input: import("@powerhousedao/ph-clint-app").ImportSpecInput) => import("@powerhousedao/ph-clint-app").ImportSpecAction;
|
|
45
|
+
bumpVersion: (input: import("@powerhousedao/ph-clint-app").BumpVersionInput) => import("@powerhousedao/ph-clint-app").BumpVersionAction;
|
|
46
|
+
publishDev: (input: import("@powerhousedao/ph-clint-app").PublishDevInput) => import("@powerhousedao/ph-clint-app").PublishDevAction;
|
|
47
|
+
publishStaging: (input: import("@powerhousedao/ph-clint-app").PublishStagingInput) => import("@powerhousedao/ph-clint-app").PublishStagingAction;
|
|
48
|
+
publishProduction: (input: import("@powerhousedao/ph-clint-app").PublishProductionInput) => import("@powerhousedao/ph-clint-app").PublishProductionAction;
|
|
49
|
+
setPublishStatus: (input: import("@powerhousedao/ph-clint-app").SetPublishStatusInput) => import("@powerhousedao/ph-clint-app").SetPublishStatusAction;
|
|
50
|
+
addExternalSkill: (input: import("@powerhousedao/ph-clint-app").AddExternalSkillInput) => import("@powerhousedao/ph-clint-app").AddExternalSkillAction;
|
|
51
|
+
removeExternalSkill: (input: import("@powerhousedao/ph-clint-app").RemoveExternalSkillInput) => import("@powerhousedao/ph-clint-app").RemoveExternalSkillAction;
|
|
52
|
+
setExternalSkillName: (input: import("@powerhousedao/ph-clint-app").SetExternalSkillNameInput) => import("@powerhousedao/ph-clint-app").SetExternalSkillNameAction;
|
|
53
|
+
setExternalSkillGithubUrl: (input: import("@powerhousedao/ph-clint-app").SetExternalSkillGithubUrlInput) => import("@powerhousedao/ph-clint-app").SetExternalSkillGithubUrlAction;
|
|
54
|
+
addPowerhousePackage: (input: import("@powerhousedao/ph-clint-app").AddPowerhousePackageInput) => import("@powerhousedao/ph-clint-app").AddPowerhousePackageAction;
|
|
55
|
+
removePowerhousePackage: (input: import("@powerhousedao/ph-clint-app").RemovePowerhousePackageInput) => import("@powerhousedao/ph-clint-app").RemovePowerhousePackageAction;
|
|
56
|
+
addPackageDocumentType: (input: import("@powerhousedao/ph-clint-app").AddPackageDocumentTypeInput) => import("@powerhousedao/ph-clint-app").AddPackageDocumentTypeAction;
|
|
57
|
+
removePackageDocumentType: (input: import("@powerhousedao/ph-clint-app").RemovePackageDocumentTypeInput) => import("@powerhousedao/ph-clint-app").RemovePackageDocumentTypeAction;
|
|
58
|
+
setPackageVersion: (input: import("@powerhousedao/ph-clint-app").SetPackageVersionInput) => import("@powerhousedao/ph-clint-app").SetPackageVersionAction;
|
|
59
|
+
enableRoutine: (input: import("@powerhousedao/ph-clint-app").EnableRoutineInput) => import("@powerhousedao/ph-clint-app").EnableRoutineAction;
|
|
60
|
+
disableRoutine: (input: import("@powerhousedao/ph-clint-app").DisableRoutineInput) => import("@powerhousedao/ph-clint-app").DisableRoutineAction;
|
|
61
|
+
setPowerhouseLevel: (input: import("@powerhousedao/ph-clint-app").SetPowerhouseLevelInput) => import("@powerhousedao/ph-clint-app").SetPowerhouseLevelAction;
|
|
62
|
+
setDescription: (input: import("@powerhousedao/ph-clint-app").SetDescriptionInput) => import("@powerhousedao/ph-clint-app").SetDescriptionAction;
|
|
63
|
+
setVersion: (input: import("@powerhousedao/ph-clint-app").SetVersionInput) => import("@powerhousedao/ph-clint-app").SetVersionAction;
|
|
64
|
+
setPackageIdentifier: (input: import("@powerhousedao/ph-clint-app").SetPackageIdentifierInput) => import("@powerhousedao/ph-clint-app").SetPackageIdentifierAction;
|
|
65
|
+
setName: (name: string | {
|
|
66
|
+
name: string;
|
|
67
|
+
}) => import("document-model").SetNameAction;
|
|
68
|
+
setPreferredEditor: (input: string | null | {
|
|
69
|
+
preferredEditor: string | null;
|
|
70
|
+
}) => import("document-model").SetPreferredEditorAction;
|
|
71
|
+
undo: (count?: number, scope?: string) => import("document-model").UndoAction;
|
|
72
|
+
redo: (count?: number, scope?: string) => import("document-model").RedoAction;
|
|
73
|
+
prune: (start?: number, end?: number, scope?: string) => import("document-model").SchemaPruneAction;
|
|
74
|
+
loadState: <TState extends import("document-model").PHBaseState = import("document-model").PHBaseState>(state: TState & {
|
|
75
|
+
name: string;
|
|
76
|
+
}, operations: number) => import("document-model").LoadStateAction;
|
|
77
|
+
noop: (scope?: string) => import("document-model").NOOPAction;
|
|
78
|
+
};
|
|
79
|
+
readonly utils: import("document-model").DocumentModelUtils<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>;
|
|
80
|
+
readonly documentModel: import("document-model").DocumentModelPHState;
|
|
81
|
+
}]>>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `publish-trigger` — watches ALL `powerhouse/ph-clint-project` documents for
|
|
3
|
+
* publish records with `Pending` status and invokes the publish pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `spec-change`, this trigger does NOT convert to a `ClintProjectSpec`.
|
|
6
|
+
* It reads `publishHistory` directly from the document state, since publish
|
|
7
|
+
* records are a doc-model concern (not a codegen input).
|
|
8
|
+
*
|
|
9
|
+
* Uses the multi-doc path (no `documentId`), scanning all matching documents
|
|
10
|
+
* on each poll. For each document with a pending publish record, it uses the
|
|
11
|
+
* project mapping to resolve the on-disk project directory.
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* 1. On document change, scans `publishHistory` for records with status
|
|
15
|
+
* `"Pending"`.
|
|
16
|
+
* 2. For each pending record, returns a work item that:
|
|
17
|
+
* a. Dispatches SET_PUBLISH_STATUS({ id, status: "InProgress" }).
|
|
18
|
+
* b. Runs `ph-clint clint-project-publish --tag <tag>`.
|
|
19
|
+
* c. Dispatches SET_PUBLISH_STATUS({ id, status: "Succeeded"|"Failed" }).
|
|
20
|
+
*/
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
|
|
23
|
+
import { setPublishStatus } from '@powerhousedao/ph-clint-app';
|
|
24
|
+
import { createDocumentChangeTrigger } from '../framework.js';
|
|
25
|
+
import { specFromDocumentState } from '../spec/from-document.js';
|
|
26
|
+
import { clintProject } from '../services/clint-project.js';
|
|
27
|
+
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
28
|
+
const TAG = '[publish]';
|
|
29
|
+
/** Map document model PublishTag values to CLI --tag values. */
|
|
30
|
+
export function tagToCli(tag) {
|
|
31
|
+
switch (tag) {
|
|
32
|
+
case 'Dev':
|
|
33
|
+
return 'dev';
|
|
34
|
+
case 'Staging':
|
|
35
|
+
return 'staging';
|
|
36
|
+
case 'Production':
|
|
37
|
+
return 'production';
|
|
38
|
+
default:
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export const publishTrigger = createDocumentChangeTrigger({
|
|
43
|
+
id: 'publish-trigger',
|
|
44
|
+
documentType: DOCUMENT_TYPE,
|
|
45
|
+
// No documentId → watches ALL ph-clint-project docs
|
|
46
|
+
// Don't fire on startup — only react to new pending records.
|
|
47
|
+
initialReconcile: false,
|
|
48
|
+
async onChange(docs, ctx) {
|
|
49
|
+
const log = ctx.context.log;
|
|
50
|
+
const folders = ctx.context.folders;
|
|
51
|
+
// Build the mapping to resolve on-disk project directories
|
|
52
|
+
const scanner = clintProject.projectScanner;
|
|
53
|
+
const scanResults = scanner
|
|
54
|
+
? scanProjects(ctx.context.workdir, scanner)
|
|
55
|
+
: [];
|
|
56
|
+
const mapping = await getProjectMapping(scanResults, folders);
|
|
57
|
+
for (const doc of docs) {
|
|
58
|
+
const publishHistory = doc.state.global.publishHistory ?? [];
|
|
59
|
+
const pending = publishHistory.filter((r) => r.status === 'Pending');
|
|
60
|
+
if (pending.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
// Resolve the on-disk target directory for this document
|
|
63
|
+
const spec = specFromDocumentState(doc.state.global, {
|
|
64
|
+
documentId: doc.header.id,
|
|
65
|
+
documentType: doc.header.documentType,
|
|
66
|
+
});
|
|
67
|
+
const entry = mapping.find((m) => m.documentId === doc.header.id);
|
|
68
|
+
const workdir = entry?.path
|
|
69
|
+
?? (spec ? path.join(ctx.context.workdir, spec.name) : ctx.context.workdir);
|
|
70
|
+
// Process the first pending record (one per tick to avoid races).
|
|
71
|
+
const record = pending[0];
|
|
72
|
+
const cliTag = tagToCli(record.tag);
|
|
73
|
+
if (!cliTag) {
|
|
74
|
+
log?.warn(`${TAG} unknown publish tag "${record.tag}" for record ${record.id}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
log?.info(`${TAG} found pending ${record.tag} publish for doc ${doc.header.id} (id=${record.id}, version=${record.version})`);
|
|
78
|
+
const { runProcess, stdout } = ctx.context;
|
|
79
|
+
return {
|
|
80
|
+
type: 'function',
|
|
81
|
+
params: {
|
|
82
|
+
fn: async () => {
|
|
83
|
+
const reactor = await ctx.reactor();
|
|
84
|
+
if (!reactor) {
|
|
85
|
+
log?.error(`${TAG} reactor not available, cannot update publish status`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
// Mark as InProgress.
|
|
89
|
+
log?.info(`${TAG} marking record ${record.id} as InProgress`);
|
|
90
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
91
|
+
setPublishStatus({ id: record.id, status: 'InProgress' }),
|
|
92
|
+
]);
|
|
93
|
+
// Install dependencies in the generated project before publishing.
|
|
94
|
+
// CI=true suppresses pnpm 11's interactive build-script approval
|
|
95
|
+
// prompt; --no-frozen-lockfile overrides CI's default so a regen
|
|
96
|
+
// that added new deps doesn't fail with ERR_PNPM_OUTDATED_LOCKFILE.
|
|
97
|
+
stdout(`${TAG} running pnpm install in ${workdir}\n`);
|
|
98
|
+
const installResult = await runProcess('pnpm install --no-frozen-lockfile', {
|
|
99
|
+
cwd: workdir,
|
|
100
|
+
timeout: 300_000,
|
|
101
|
+
env: { CI: 'true' },
|
|
102
|
+
});
|
|
103
|
+
if (!installResult.success) {
|
|
104
|
+
log?.error(`${TAG} pnpm install failed in ${workdir}`);
|
|
105
|
+
}
|
|
106
|
+
// Run the publish command.
|
|
107
|
+
let success = false;
|
|
108
|
+
try {
|
|
109
|
+
stdout(`${TAG} invoking clint-project-publish --tag ${cliTag}\n`);
|
|
110
|
+
const { clintProjectPublish } = await import('../commands/clint-project-publish.js');
|
|
111
|
+
const result = await clintProjectPublish.execute({ tag: cliTag, dir: workdir, dryRun: false, skipBuild: false, skipGitCheck: true, verbose: false }, ctx.commandContext);
|
|
112
|
+
success = !result.text.includes('failed');
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
log?.error(`${TAG} publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
}
|
|
117
|
+
// Update status.
|
|
118
|
+
const finalStatus = success ? 'Succeeded' : 'Failed';
|
|
119
|
+
stdout(`${TAG} marking record ${record.id} as ${finalStatus}\n`);
|
|
120
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
121
|
+
setPublishStatus({
|
|
122
|
+
id: record.id,
|
|
123
|
+
status: finalStatus,
|
|
124
|
+
}),
|
|
125
|
+
]);
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
callbacks: {
|
|
129
|
+
onFailure: (err) => {
|
|
130
|
+
log?.error(`${TAG} publish trigger failed: ${err.message}`);
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
},
|
|
137
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ImportSpecInput } from '@powerhousedao/ph-clint-app/document-models/ph-clint-project';
|
|
2
|
+
import type { ClintProjectSpec } from '../spec/types.js';
|
|
3
|
+
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
4
|
+
* Excludes documentId/documentType — they don't affect codegen output. */
|
|
5
|
+
export declare function hashSpec(spec: ClintProjectSpec): string;
|
|
6
|
+
export declare function specToImportInput(spec: ClintProjectSpec): ImportSpecInput;
|
|
7
|
+
export declare const specChangeTrigger: import("@powerhousedao/ph-clint").Trigger<{
|
|
8
|
+
pending: number;
|
|
9
|
+
}, {
|
|
10
|
+
model: string;
|
|
11
|
+
devServicePort: number;
|
|
12
|
+
phVersion: string;
|
|
13
|
+
apiKey?: string | undefined;
|
|
14
|
+
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [{
|
|
15
|
+
readonly version: 1;
|
|
16
|
+
readonly reducer: import("document-model").Reducer<import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PhClintProjectPHState>;
|
|
17
|
+
readonly actions: {
|
|
18
|
+
setAgentModel: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetAgentModelInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetAgentModelAction;
|
|
19
|
+
addAgentProfileRef: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentProfileRefInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentProfileRefAction;
|
|
20
|
+
removeAgentProfileRef: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentProfileRefInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentProfileRefAction;
|
|
21
|
+
reorderAgentProfileRefs: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ReorderAgentProfileRefsInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ReorderAgentProfileRefsAction;
|
|
22
|
+
addAgentSkill: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentSkillInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentSkillAction;
|
|
23
|
+
removeAgentSkill: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentSkillInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentSkillAction;
|
|
24
|
+
addAgentToolPattern: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentToolPatternInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddAgentToolPatternAction;
|
|
25
|
+
removeAgentToolPattern: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentToolPatternInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveAgentToolPatternAction;
|
|
26
|
+
addSubAgent: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddSubAgentInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddSubAgentAction;
|
|
27
|
+
removeSubAgent: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveSubAgentInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveSubAgentAction;
|
|
28
|
+
setSubAgentName: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetSubAgentNameInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetSubAgentNameAction;
|
|
29
|
+
setSubAgentDescription: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetSubAgentDescriptionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetSubAgentDescriptionAction;
|
|
30
|
+
setMainAgentName: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentNameInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentNameAction;
|
|
31
|
+
setMainAgentDescription: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentDescriptionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentDescriptionAction;
|
|
32
|
+
clearMainAgentDescription: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ClearMainAgentDescriptionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ClearMainAgentDescriptionAction;
|
|
33
|
+
setMainAgentImage: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentImageInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetMainAgentImageAction;
|
|
34
|
+
clearMainAgentImage: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ClearMainAgentImageInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ClearMainAgentImageAction;
|
|
35
|
+
addProfile: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddProfileInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddProfileAction;
|
|
36
|
+
updateProfile: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").UpdateProfileInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").UpdateProfileAction;
|
|
37
|
+
removeProfile: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveProfileInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveProfileAction;
|
|
38
|
+
reorderProfiles: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ReorderProfilesInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ReorderProfilesAction;
|
|
39
|
+
addModel: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddModelInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddModelAction;
|
|
40
|
+
removeModel: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveModelInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveModelAction;
|
|
41
|
+
enableMastra: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").EnableMastraInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").EnableMastraAction;
|
|
42
|
+
disableMastra: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").DisableMastraInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").DisableMastraAction;
|
|
43
|
+
setEnableChat: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetEnableChatInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetEnableChatAction;
|
|
44
|
+
addSupportedResource: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddSupportedResourceInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddSupportedResourceAction;
|
|
45
|
+
removeSupportedResource: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveSupportedResourceInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveSupportedResourceAction;
|
|
46
|
+
setProxyEnabled: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetProxyEnabledInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetProxyEnabledAction;
|
|
47
|
+
setObservabilityEnabled: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetObservabilityEnabledInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetObservabilityEnabledAction;
|
|
48
|
+
importSpec: (input: ImportSpecInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").ImportSpecAction;
|
|
49
|
+
bumpVersion: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").BumpVersionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").BumpVersionAction;
|
|
50
|
+
publishDev: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishDevInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishDevAction;
|
|
51
|
+
publishStaging: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishStagingInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishStagingAction;
|
|
52
|
+
publishProduction: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishProductionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PublishProductionAction;
|
|
53
|
+
setPublishStatus: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPublishStatusInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPublishStatusAction;
|
|
54
|
+
addExternalSkill: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddExternalSkillInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddExternalSkillAction;
|
|
55
|
+
removeExternalSkill: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveExternalSkillInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemoveExternalSkillAction;
|
|
56
|
+
setExternalSkillName: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetExternalSkillNameInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetExternalSkillNameAction;
|
|
57
|
+
setExternalSkillGithubUrl: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetExternalSkillGithubUrlInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetExternalSkillGithubUrlAction;
|
|
58
|
+
addPowerhousePackage: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddPowerhousePackageInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddPowerhousePackageAction;
|
|
59
|
+
removePowerhousePackage: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemovePowerhousePackageInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemovePowerhousePackageAction;
|
|
60
|
+
addPackageDocumentType: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddPackageDocumentTypeInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").AddPackageDocumentTypeAction;
|
|
61
|
+
removePackageDocumentType: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemovePackageDocumentTypeInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").RemovePackageDocumentTypeAction;
|
|
62
|
+
setPackageVersion: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPackageVersionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPackageVersionAction;
|
|
63
|
+
enableRoutine: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").EnableRoutineInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").EnableRoutineAction;
|
|
64
|
+
disableRoutine: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").DisableRoutineInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").DisableRoutineAction;
|
|
65
|
+
setPowerhouseLevel: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPowerhouseLevelInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPowerhouseLevelAction;
|
|
66
|
+
setDescription: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetDescriptionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetDescriptionAction;
|
|
67
|
+
setVersion: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetVersionInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetVersionAction;
|
|
68
|
+
setPackageIdentifier: (input: import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPackageIdentifierInput) => import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").SetPackageIdentifierAction;
|
|
69
|
+
setName: (name: string | {
|
|
70
|
+
name: string;
|
|
71
|
+
}) => import("document-model").SetNameAction;
|
|
72
|
+
setPreferredEditor: (input: string | null | {
|
|
73
|
+
preferredEditor: string | null;
|
|
74
|
+
}) => import("document-model").SetPreferredEditorAction;
|
|
75
|
+
undo: (count?: number, scope?: string) => import("document-model").UndoAction;
|
|
76
|
+
redo: (count?: number, scope?: string) => import("document-model").RedoAction;
|
|
77
|
+
prune: (start?: number, end?: number, scope?: string) => import("document-model").SchemaPruneAction;
|
|
78
|
+
loadState: <TState extends import("document-model").PHBaseState = import("document-model").PHBaseState>(state: TState & {
|
|
79
|
+
name: string;
|
|
80
|
+
}, operations: number) => import("document-model").LoadStateAction;
|
|
81
|
+
noop: (scope?: string) => import("document-model").NOOPAction;
|
|
82
|
+
};
|
|
83
|
+
readonly utils: import("document-model").DocumentModelUtils<import("@powerhousedao/ph-clint-app/document-models/ph-clint-project").PhClintProjectPHState>;
|
|
84
|
+
readonly documentModel: import("document-model").DocumentModelPHState;
|
|
85
|
+
}]>>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spec-change` trigger — watches ALL `powerhouse/ph-clint-project` documents
|
|
3
|
+
* and, when any change, re-runs the code generator for each affected project.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. `createDocumentChangeTrigger` (from ph-clint) owns the subscription,
|
|
7
|
+
* event coalescing, doc loading, and type narrowing. No `documentId` is
|
|
8
|
+
* set, so it loads ALL matching documents on each poll.
|
|
9
|
+
* 2. `getProjectMapping()` merges an on-disk scan (via the clint-project
|
|
10
|
+
* service's projectScanner) with personal-drive folder entries to
|
|
11
|
+
* correlate each document to an on-disk project directory.
|
|
12
|
+
* 3. For each document, `specFromDocumentState()` builds a spec, hashes it,
|
|
13
|
+
* and compares against `.ph/ph-clint-cli/.last-spec-hash`. If unchanged,
|
|
14
|
+
* skips. Otherwise regenerates at the mapped target directory.
|
|
15
|
+
*/
|
|
16
|
+
import crypto from 'node:crypto';
|
|
17
|
+
import fs from 'node:fs/promises';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { scanProjects, getProjectMapping } from '@powerhousedao/ph-clint';
|
|
20
|
+
import { createDocumentChangeTrigger } from '../framework.js';
|
|
21
|
+
import { generateProject } from '../codegen/index.js';
|
|
22
|
+
import { runPostGenActions } from '@powerhousedao/ph-clint-dev/codegen/actions';
|
|
23
|
+
import { specFromDocumentState } from '../spec/from-document.js';
|
|
24
|
+
import { readProjectSpec, writeProjectSpec } from '../spec/file.js';
|
|
25
|
+
import { ensureSpecDocument } from '../spec/ensure-document.js';
|
|
26
|
+
import { clintProject } from '../services/clint-project.js';
|
|
27
|
+
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
28
|
+
const HASH_DIR = path.join('.ph', 'ph-clint-cli');
|
|
29
|
+
const HASH_FILE = '.last-spec-hash';
|
|
30
|
+
const TAG = '[spec-change]';
|
|
31
|
+
function getHashPath(targetDir) {
|
|
32
|
+
return path.join(targetDir, HASH_DIR, HASH_FILE);
|
|
33
|
+
}
|
|
34
|
+
function isEnoent(err) {
|
|
35
|
+
return (!!err &&
|
|
36
|
+
typeof err === 'object' &&
|
|
37
|
+
'code' in err &&
|
|
38
|
+
err.code === 'ENOENT');
|
|
39
|
+
}
|
|
40
|
+
async function readLastSpecHash(targetDir) {
|
|
41
|
+
try {
|
|
42
|
+
return (await fs.readFile(getHashPath(targetDir), 'utf8')).trim() || null;
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
if (isEnoent(err))
|
|
46
|
+
return null;
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function writeLastSpecHash(targetDir, hash) {
|
|
51
|
+
const file = getHashPath(targetDir);
|
|
52
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
53
|
+
await fs.writeFile(file, hash + '\n', 'utf8');
|
|
54
|
+
}
|
|
55
|
+
/** Stable hash of a ClintProjectSpec (sha256 of canonical-key JSON).
|
|
56
|
+
* Excludes documentId/documentType — they don't affect codegen output. */
|
|
57
|
+
export function hashSpec(spec) {
|
|
58
|
+
const { documentId, documentType, ...codegenRelevant } = spec;
|
|
59
|
+
return crypto
|
|
60
|
+
.createHash('sha256')
|
|
61
|
+
.update(canonicalJson(codegenRelevant), 'utf8')
|
|
62
|
+
.digest('hex');
|
|
63
|
+
}
|
|
64
|
+
function canonicalJson(value) {
|
|
65
|
+
if (value === null || typeof value !== 'object')
|
|
66
|
+
return JSON.stringify(value);
|
|
67
|
+
if (Array.isArray(value)) {
|
|
68
|
+
return '[' + value.map(canonicalJson).join(',') + ']';
|
|
69
|
+
}
|
|
70
|
+
const obj = value;
|
|
71
|
+
const keys = Object.keys(obj).sort();
|
|
72
|
+
return ('{' +
|
|
73
|
+
keys
|
|
74
|
+
.map((k) => JSON.stringify(k) + ':' + canonicalJson(obj[k]))
|
|
75
|
+
.join(',') +
|
|
76
|
+
'}');
|
|
77
|
+
}
|
|
78
|
+
export function specToImportInput(spec) {
|
|
79
|
+
const main = spec.features.mastra.mainAgent;
|
|
80
|
+
return {
|
|
81
|
+
name: spec.name,
|
|
82
|
+
scope: spec.scope ?? null,
|
|
83
|
+
version: spec.version,
|
|
84
|
+
description: spec.description,
|
|
85
|
+
powerhouse: spec.features.powerhouse,
|
|
86
|
+
mastraEnabled: spec.features.mastra.enabled,
|
|
87
|
+
routineEnabled: spec.features.routine.enabled,
|
|
88
|
+
mainAgent: main
|
|
89
|
+
? {
|
|
90
|
+
id: main.id,
|
|
91
|
+
name: main.name,
|
|
92
|
+
description: main.description,
|
|
93
|
+
attachment: main.image,
|
|
94
|
+
modelId: main.modelId,
|
|
95
|
+
profileIds: [...main.profileIds],
|
|
96
|
+
skills: [...main.skills],
|
|
97
|
+
toolPatterns: [...main.toolPatterns],
|
|
98
|
+
}
|
|
99
|
+
: null,
|
|
100
|
+
subAgents: spec.features.mastra.subAgents.map((s) => ({
|
|
101
|
+
id: s.id,
|
|
102
|
+
name: s.name,
|
|
103
|
+
description: s.description,
|
|
104
|
+
modelId: s.modelId,
|
|
105
|
+
profileIds: [...s.profileIds],
|
|
106
|
+
skills: [...s.skills],
|
|
107
|
+
toolPatterns: [...s.toolPatterns],
|
|
108
|
+
})),
|
|
109
|
+
models: spec.features.mastra.models.map((m) => ({
|
|
110
|
+
id: m.id,
|
|
111
|
+
})),
|
|
112
|
+
profiles: spec.features.mastra.profiles.map((p) => ({
|
|
113
|
+
id: p.id,
|
|
114
|
+
title: p.title,
|
|
115
|
+
content: p.content,
|
|
116
|
+
})),
|
|
117
|
+
packages: spec.packages.map((p) => ({
|
|
118
|
+
id: p.id,
|
|
119
|
+
packageName: p.packageName,
|
|
120
|
+
documentTypes: p.documentTypes,
|
|
121
|
+
})),
|
|
122
|
+
externalSkills: spec.externalSkills.map((s) => ({
|
|
123
|
+
id: s.id,
|
|
124
|
+
name: s.name,
|
|
125
|
+
githubUrl: s.githubUrl,
|
|
126
|
+
})),
|
|
127
|
+
enableChat: spec.features.mastra.common.enableChat,
|
|
128
|
+
proxyEnabled: spec.deployment.proxyEnabled,
|
|
129
|
+
observabilityEnabled: spec.deployment.observabilityEnabled,
|
|
130
|
+
supportedResources: spec.deployment.supportedResources,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export const specChangeTrigger = createDocumentChangeTrigger({
|
|
134
|
+
id: 'spec-change',
|
|
135
|
+
documentType: DOCUMENT_TYPE,
|
|
136
|
+
// No documentId → watches ALL ph-clint-project docs
|
|
137
|
+
callOnEmpty: true, // bootstrap from disk when no documents exist yet
|
|
138
|
+
async onChange(docs, ctx) {
|
|
139
|
+
const log = ctx.context.log;
|
|
140
|
+
const folders = ctx.context.folders;
|
|
141
|
+
// Build the mapping: on-disk projects + personal drive documents
|
|
142
|
+
const scanner = clintProject.projectScanner;
|
|
143
|
+
const scanResults = scanner
|
|
144
|
+
? scanProjects(ctx.context.workdir, scanner)
|
|
145
|
+
: [];
|
|
146
|
+
const mapping = await getProjectMapping(scanResults, folders);
|
|
147
|
+
// Auto-create spec documents for unlinked or orphaned projects
|
|
148
|
+
const reactor = await ctx.reactor();
|
|
149
|
+
if (reactor && folders) {
|
|
150
|
+
for (const entry of mapping) {
|
|
151
|
+
if (!entry.path)
|
|
152
|
+
continue;
|
|
153
|
+
const existingSpec = await readProjectSpec(entry.path);
|
|
154
|
+
if (!existingSpec)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
const { docId, created } = await ensureSpecDocument({
|
|
158
|
+
spec: existingSpec,
|
|
159
|
+
targetDir: entry.path,
|
|
160
|
+
reactor,
|
|
161
|
+
folders,
|
|
162
|
+
});
|
|
163
|
+
if (created) {
|
|
164
|
+
log?.info(`${TAG} linked project ${entry.name} to document ${docId}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
log?.warn(`${TAG} could not ensure spec document for ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const doc of docs) {
|
|
173
|
+
const spec = specFromDocumentState(doc.state.global, {
|
|
174
|
+
documentId: doc.header.id,
|
|
175
|
+
documentType: doc.header.documentType,
|
|
176
|
+
});
|
|
177
|
+
if (!spec) {
|
|
178
|
+
log?.debug(`${TAG} document ${doc.header.id} state did not validate against ClintProjectSpec, skipping`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Find target directory from mapping
|
|
182
|
+
const entry = mapping.find((m) => m.documentId === doc.header.id);
|
|
183
|
+
const targetDir = entry?.path ?? path.join(ctx.context.workdir, spec.name);
|
|
184
|
+
// Per-project hash comparison
|
|
185
|
+
const nextHash = hashSpec(spec);
|
|
186
|
+
const prevHash = await readLastSpecHash(targetDir);
|
|
187
|
+
if (nextHash === prevHash) {
|
|
188
|
+
log?.debug(`${TAG} spec for ${spec.name} unchanged (hash=${nextHash.slice(0, 12)}…), skipping`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
log?.debug(`${TAG} spec for ${spec.name} changed: ${prevHash ? prevHash.slice(0, 12) + '…' : '<none>'} → ${nextHash.slice(0, 12)}…`);
|
|
192
|
+
log?.info(`${TAG} regenerating ${spec.name} at ${targetDir} (doc=${doc.header.id})`);
|
|
193
|
+
const t0 = Date.now();
|
|
194
|
+
const warnings = [];
|
|
195
|
+
const result = await generateProject({
|
|
196
|
+
targetDir,
|
|
197
|
+
spec,
|
|
198
|
+
mode: 'update',
|
|
199
|
+
force: true,
|
|
200
|
+
onWarn: (m) => warnings.push(m),
|
|
201
|
+
});
|
|
202
|
+
// Write spec JSON with documentId
|
|
203
|
+
await writeProjectSpec(targetDir, spec);
|
|
204
|
+
await writeLastSpecHash(targetDir, nextHash);
|
|
205
|
+
const elapsed = Date.now() - t0;
|
|
206
|
+
if (log) {
|
|
207
|
+
if (result.migrated)
|
|
208
|
+
log.info(`${TAG} migrated flat → split layout`);
|
|
209
|
+
log.info(`${TAG} regenerated ${spec.name} in ${elapsed}ms: ${result.files.length} written, ${result.skipped.length} skipped, ${result.deleted.length} deleted`);
|
|
210
|
+
for (const w of warnings)
|
|
211
|
+
log.warn(`${TAG} ${w}`);
|
|
212
|
+
}
|
|
213
|
+
// Run post-generation actions (ph-init, install, build, skills-sync).
|
|
214
|
+
// Route through ctx.context.stdout so output flows through
|
|
215
|
+
// routine.onOutput → service log buffer → ServicePanel.
|
|
216
|
+
await runPostGenActions(result.pendingActions, {
|
|
217
|
+
log: (msg) => ctx.context.stdout(`${TAG} ${msg}\n`),
|
|
218
|
+
runProcess: ctx.context.runProcess,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Work done inline, no additional work item needed
|
|
222
|
+
return null;
|
|
223
|
+
},
|
|
224
|
+
});
|