@urielsh/prodify 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/.prodify/AGENTS.md +56 -0
- package/.prodify/README.md +10 -0
- package/.prodify/artifacts/01-understand.md +28 -0
- package/.prodify/artifacts/02-diagnose.md +26 -0
- package/.prodify/artifacts/03-architecture.md +30 -0
- package/.prodify/artifacts/04-plan.md +35 -0
- package/.prodify/artifacts/05-refactor.md +24 -0
- package/.prodify/artifacts/06-validate.md +17 -0
- package/.prodify/artifacts/README.md +6 -0
- package/.prodify/artifacts/architecture_spec.md +29 -0
- package/.prodify/artifacts/artifact-validation-design.md +276 -0
- package/.prodify/artifacts/cli-command-design.md +299 -0
- package/.prodify/artifacts/completed-steps-tracking.md +201 -0
- package/.prodify/artifacts/diagnostic_report.md +45 -0
- package/.prodify/artifacts/hardening-patch-summary.md +57 -0
- package/.prodify/artifacts/hardening-verification-report.md +48 -0
- package/.prodify/artifacts/implementation_summary.md +19 -0
- package/.prodify/artifacts/improvement-evaluation-spec.md +148 -0
- package/.prodify/artifacts/next-step-resolver-design.md +570 -0
- package/.prodify/artifacts/orientation_map.md +32 -0
- package/.prodify/artifacts/persona-removal-audit.md +106 -0
- package/.prodify/artifacts/planning-alignment-report.md +189 -0
- package/.prodify/artifacts/refactor-plan-template-hardening.md +83 -0
- package/.prodify/artifacts/refactor-validate-loop-design.md +231 -0
- package/.prodify/artifacts/refactor_plan.md +21 -0
- package/.prodify/artifacts/refactor_plan.strict-example.md +31 -0
- package/.prodify/artifacts/run-state-design.md +292 -0
- package/.prodify/artifacts/run-summary-spec.md +149 -0
- package/.prodify/artifacts/run_state.json +14 -0
- package/.prodify/artifacts/sample-repo-test-plan.md +129 -0
- package/.prodify/artifacts/status-output-spec.md +349 -0
- package/.prodify/artifacts/step-selection-design.md +251 -0
- package/.prodify/artifacts/task-dispatcher-design.md +266 -0
- package/.prodify/artifacts/task-header-audit.md +42 -0
- package/.prodify/artifacts/task-log-enhancement-spec.md +264 -0
- package/.prodify/artifacts/task-protocol-hardening-plan.md +68 -0
- package/.prodify/artifacts/task-self-validation-spec.md +171 -0
- package/.prodify/artifacts/task_log.json +160 -0
- package/.prodify/artifacts/template-usage-audit.md +20 -0
- package/.prodify/artifacts/validation_report.md +22 -0
- package/.prodify/contracts/README.md +3 -0
- package/.prodify/contracts/architecture.contract.json +42 -0
- package/.prodify/contracts/diagnose.contract.json +42 -0
- package/.prodify/contracts/plan.contract.json +42 -0
- package/.prodify/contracts/refactor.contract.json +45 -0
- package/.prodify/contracts/understand.contract.json +42 -0
- package/.prodify/contracts/validate.contract.json +52 -0
- package/.prodify/contracts-src/README.md +4 -0
- package/.prodify/contracts-src/architecture.contract.md +29 -0
- package/.prodify/contracts-src/diagnose.contract.md +29 -0
- package/.prodify/contracts-src/plan.contract.md +29 -0
- package/.prodify/contracts-src/refactor.contract.md +32 -0
- package/.prodify/contracts-src/understand.contract.md +29 -0
- package/.prodify/contracts-src/validate.contract.md +35 -0
- package/.prodify/metrics/README.md +4 -0
- package/.prodify/planning.md +13 -0
- package/.prodify/project.md +15 -0
- package/.prodify/rules/01-global-operating-rules.md +21 -0
- package/.prodify/rules/02-analysis-discipline.md +17 -0
- package/.prodify/rules/03-diagnosis-severity-rules.md +42 -0
- package/.prodify/rules/04-architecture-rules.md +27 -0
- package/.prodify/rules/05-planning-rules.md +23 -0
- package/.prodify/rules/06-refactor-rules.md +20 -0
- package/.prodify/rules/07-validation-rules.md +25 -0
- package/.prodify/rules/08-output-format-rules.md +20 -0
- package/.prodify/rules/09-template-usage-rules.md +11 -0
- package/.prodify/rules/10-artifact-flow-and-orchestration-rules.md +40 -0
- package/.prodify/rules/README.md +3 -0
- package/.prodify/rules/example-rule.md +4 -0
- package/.prodify/runtime-commands.md +57 -0
- package/.prodify/skills/README.md +8 -0
- package/.prodify/skills/domain/react-frontend.skill.json +34 -0
- package/.prodify/skills/domain/typescript-backend.skill.json +34 -0
- package/.prodify/skills/quality-policy/maintainability-review.skill.json +25 -0
- package/.prodify/skills/quality-policy/security-hardening.skill.json +32 -0
- package/.prodify/skills/quality-policy/test-hardening.skill.json +23 -0
- package/.prodify/skills/registry.json +16 -0
- package/.prodify/skills/stage-method/architecture-method.skill.json +22 -0
- package/.prodify/skills/stage-method/codebase-scanning.skill.json +22 -0
- package/.prodify/skills/stage-method/diagnosis-method.skill.json +22 -0
- package/.prodify/skills/stage-method/planning-method.skill.json +22 -0
- package/.prodify/skills/stage-method/refactoring-method.skill.json +22 -0
- package/.prodify/skills/stage-method/validation-method.skill.json +22 -0
- package/.prodify/state.json +30 -0
- package/.prodify/tasks/01-understand.md +83 -0
- package/.prodify/tasks/02-diagnose.md +67 -0
- package/.prodify/tasks/03-architecture.md +70 -0
- package/.prodify/tasks/04-plan.md +71 -0
- package/.prodify/tasks/05-refactor.md +61 -0
- package/.prodify/tasks/06-validate.md +69 -0
- package/.prodify/tasks/README.md +3 -0
- package/.prodify/tasks/example-task.md +13 -0
- package/.prodify/templates/01-understand.template.md +18 -0
- package/.prodify/templates/02-diagnose.template.md +18 -0
- package/.prodify/templates/03-architecture.template.md +18 -0
- package/.prodify/templates/04-plan.template.md +22 -0
- package/.prodify/templates/05-refactor.template.md +19 -0
- package/.prodify/templates/06-validate.template.md +16 -0
- package/.prodify/templates/README.md +3 -0
- package/.prodify/templates/architecture_spec.template.md +29 -0
- package/.prodify/templates/diagnostic_report.template.md +45 -0
- package/.prodify/templates/example.template.md +5 -0
- package/.prodify/templates/implementation_summary.template.md +19 -0
- package/.prodify/templates/orientation_map.template.md +32 -0
- package/.prodify/templates/refactor_plan.template.md +24 -0
- package/.prodify/templates/validation_report.template.md +22 -0
- package/.prodify/version.json +5 -0
- package/AGENTS.md +305 -0
- package/LICENSE +201 -0
- package/README.md +118 -0
- package/assets/presets/default/canonical/AGENTS.md +54 -0
- package/assets/presets/default/canonical/artifacts/README.md +6 -0
- package/assets/presets/default/canonical/contracts-src/README.md +4 -0
- package/assets/presets/default/canonical/contracts-src/architecture.contract.md +56 -0
- package/assets/presets/default/canonical/contracts-src/diagnose.contract.md +49 -0
- package/assets/presets/default/canonical/contracts-src/plan.contract.md +42 -0
- package/assets/presets/default/canonical/contracts-src/refactor.contract.md +54 -0
- package/assets/presets/default/canonical/contracts-src/understand.contract.md +56 -0
- package/assets/presets/default/canonical/contracts-src/validate.contract.md +64 -0
- package/assets/presets/default/canonical/metrics/README.md +4 -0
- package/assets/presets/default/canonical/planning.md +13 -0
- package/assets/presets/default/canonical/project.md +15 -0
- package/assets/presets/default/canonical/rules/README.md +3 -0
- package/assets/presets/default/canonical/rules/example-rule.md +4 -0
- package/assets/presets/default/canonical/runtime-commands.md +57 -0
- package/assets/presets/default/canonical/skills/README.md +8 -0
- package/assets/presets/default/canonical/skills/domain/react-frontend.skill.json +34 -0
- package/assets/presets/default/canonical/skills/domain/typescript-backend.skill.json +34 -0
- package/assets/presets/default/canonical/skills/quality-policy/maintainability-review.skill.json +25 -0
- package/assets/presets/default/canonical/skills/quality-policy/security-hardening.skill.json +32 -0
- package/assets/presets/default/canonical/skills/quality-policy/test-hardening.skill.json +23 -0
- package/assets/presets/default/canonical/skills/registry.json +16 -0
- package/assets/presets/default/canonical/skills/stage-method/architecture-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/codebase-scanning.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/diagnosis-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/planning-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/refactoring-method.skill.json +22 -0
- package/assets/presets/default/canonical/skills/stage-method/validation-method.skill.json +22 -0
- package/assets/presets/default/canonical/state.json +30 -0
- package/assets/presets/default/canonical/tasks/README.md +3 -0
- package/assets/presets/default/canonical/tasks/example-task.md +13 -0
- package/assets/presets/default/canonical/templates/README.md +3 -0
- package/assets/presets/default/canonical/templates/example.template.md +5 -0
- package/assets/presets/default/preset.json +5 -0
- package/dist/cli.js +53 -0
- package/dist/commands/doctor.js +16 -0
- package/dist/commands/init.js +30 -0
- package/dist/commands/install.js +27 -0
- package/dist/commands/setup-agent.js +23 -0
- package/dist/commands/status.js +28 -0
- package/dist/commands/sync.js +29 -0
- package/dist/commands/update.js +18 -0
- package/dist/contracts/compiled-schema.js +37 -0
- package/dist/contracts/compiler.js +83 -0
- package/dist/contracts/freshness.js +52 -0
- package/dist/contracts/index.js +5 -0
- package/dist/contracts/parser.js +201 -0
- package/dist/contracts/schema.js +138 -0
- package/dist/contracts/source-schema.js +111 -0
- package/dist/core/agent-runtime.js +36 -0
- package/dist/core/agent-setup.js +111 -0
- package/dist/core/doctor.js +155 -0
- package/dist/core/errors.js +19 -0
- package/dist/core/flow-state.js +262 -0
- package/dist/core/fs.js +50 -0
- package/dist/core/install.js +44 -0
- package/dist/core/managed-files.js +106 -0
- package/dist/core/paths.js +56 -0
- package/dist/core/preset-validation.js +43 -0
- package/dist/core/prompt-builder.js +63 -0
- package/dist/core/repo-context.js +77 -0
- package/dist/core/repo-root.js +55 -0
- package/dist/core/skill-resolution.js +123 -0
- package/dist/core/state.js +220 -0
- package/dist/core/status.js +293 -0
- package/dist/core/sync.js +63 -0
- package/dist/core/targets.js +48 -0
- package/dist/core/upgrade.js +57 -0
- package/dist/core/validation.js +138 -0
- package/dist/core/version-checks.js +35 -0
- package/dist/generators/claude.js +14 -0
- package/dist/generators/codex.js +14 -0
- package/dist/generators/copilot.js +29 -0
- package/dist/generators/header.js +13 -0
- package/dist/generators/opencode.js +14 -0
- package/dist/generators/shared.js +37 -0
- package/dist/index.js +9 -0
- package/dist/legacy/targets.js +55 -0
- package/dist/presets/default.js +3 -0
- package/dist/presets/loader.js +34 -0
- package/dist/presets/version.js +18 -0
- package/dist/scoring/model.js +262 -0
- package/dist/skills/loader.js +40 -0
- package/dist/skills/schema.js +159 -0
- package/dist/types.js +1 -0
- package/docs/canonical-prodify-layout.md +87 -0
- package/docs/claude-support.md +22 -0
- package/docs/cli-doctor-spec.md +52 -0
- package/docs/cli-init-spec.md +70 -0
- package/docs/cli-install-agent-spec.md +48 -0
- package/docs/cli-sync-spec.md +40 -0
- package/docs/codex-support.md +24 -0
- package/docs/compatibility-targets.md +59 -0
- package/docs/copilot-support.md +30 -0
- package/docs/default-preset.md +47 -0
- package/docs/generated-file-headers.md +45 -0
- package/docs/generation-rules.md +94 -0
- package/docs/idempotency-guarantees.md +31 -0
- package/docs/managed-file-detection.md +45 -0
- package/docs/manual-edit-conflicts.md +37 -0
- package/docs/opencode-support.md +27 -0
- package/docs/ownership-rules.md +39 -0
- package/docs/path-resolution-rules.md +40 -0
- package/docs/preset-structure.md +49 -0
- package/docs/skill-system.md +67 -0
- package/docs/versioning-and-upgrade-strategy.md +40 -0
- package/package.json +22 -0
- package/src/README.md +11 -0
- package/src/cli.ts +61 -0
- package/src/commands/doctor.ts +20 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/setup-agent.ts +28 -0
- package/src/commands/status.ts +34 -0
- package/src/commands/update.ts +23 -0
- package/src/contracts/README.md +10 -0
- package/src/contracts/compiled-schema.ts +42 -0
- package/src/contracts/compiler.ts +111 -0
- package/src/contracts/freshness.ts +58 -0
- package/src/contracts/index.ts +11 -0
- package/src/contracts/parser.ts +253 -0
- package/src/contracts/source-schema.ts +141 -0
- package/src/core/agent-runtime.ts +53 -0
- package/src/core/agent-setup.ts +147 -0
- package/src/core/doctor.ts +171 -0
- package/src/core/errors.ts +28 -0
- package/src/core/flow-state.ts +333 -0
- package/src/core/fs.ts +59 -0
- package/src/core/paths.ts +63 -0
- package/src/core/preset-validation.ts +47 -0
- package/src/core/prompt-builder.ts +73 -0
- package/src/core/repo-context.ts +93 -0
- package/src/core/repo-root.ts +74 -0
- package/src/core/skill-resolution.ts +151 -0
- package/src/core/state.ts +264 -0
- package/src/core/status.ts +372 -0
- package/src/core/targets.ts +53 -0
- package/src/core/upgrade.ts +66 -0
- package/src/core/validation.ts +233 -0
- package/src/core/version-checks.ts +40 -0
- package/src/index.ts +13 -0
- package/src/presets/default.ts +7 -0
- package/src/presets/loader.ts +46 -0
- package/src/presets/version.ts +31 -0
- package/src/scoring/model.ts +332 -0
- package/src/skills/loader.ts +58 -0
- package/src/skills/schema.ts +197 -0
- package/src/types.ts +329 -0
- package/tests/integration/cli-flows.test.js +347 -0
- package/tests/unit/agent-setup.test.js +81 -0
- package/tests/unit/cli.test.js +28 -0
- package/tests/unit/contracts.test.js +61 -0
- package/tests/unit/helpers.js +28 -0
- package/tests/unit/paths.test.js +52 -0
- package/tests/unit/preset-loader.test.js +37 -0
- package/tests/unit/scoring.test.js +65 -0
- package/tests/unit/self-hosted-workspace.test.js +22 -0
- package/tests/unit/skills.test.js +115 -0
- package/tests/unit/state-and-flow.test.js +120 -0
- package/tests/unit/targets.test.js +13 -0
- package/tests/unit/validation.test.js +73 -0
- package/tests/unit/version.test.js +24 -0
- package/tsconfig.json +23 -0
- package/urielsh-prodify-0.1.0.tgz +0 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildManagedMarkdownOutput, readCanonicalMarkdown, stripLeadingTitle } from './shared.js';
|
|
2
|
+
import { LEGACY_TARGET_PATH_DEFINITIONS } from '../core/paths.js';
|
|
3
|
+
export async function generateCopilotContent(repoRoot) {
|
|
4
|
+
const target = LEGACY_TARGET_PATH_DEFINITIONS.copilot;
|
|
5
|
+
const agentsMarkdown = await readCanonicalMarkdown(repoRoot, '.prodify/AGENTS.md');
|
|
6
|
+
const projectMarkdown = await readCanonicalMarkdown(repoRoot, '.prodify/project.md');
|
|
7
|
+
const runtimeMarkdown = await readCanonicalMarkdown(repoRoot, '.prodify/runtime-commands.md');
|
|
8
|
+
const body = [
|
|
9
|
+
'# Copilot Instructions',
|
|
10
|
+
'',
|
|
11
|
+
'## Project Context',
|
|
12
|
+
'',
|
|
13
|
+
stripLeadingTitle(projectMarkdown),
|
|
14
|
+
'',
|
|
15
|
+
'## Operating Guidance',
|
|
16
|
+
'',
|
|
17
|
+
stripLeadingTitle(agentsMarkdown),
|
|
18
|
+
'',
|
|
19
|
+
'## Runtime Commands',
|
|
20
|
+
'',
|
|
21
|
+
stripLeadingTitle(runtimeMarkdown)
|
|
22
|
+
].join('\n').trimEnd() + '\n';
|
|
23
|
+
return buildManagedMarkdownOutput({
|
|
24
|
+
agent: target.agent,
|
|
25
|
+
canonicalSources: target.canonicalSources,
|
|
26
|
+
regenerateCommand: 'prodify update',
|
|
27
|
+
body
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { renderCanonicalSourceList } from '../core/managed-files.js';
|
|
2
|
+
export function renderManagedFileHeader({ agent, canonicalSources, regenerateCommand, bodyFingerprint }) {
|
|
3
|
+
return [
|
|
4
|
+
'<!--',
|
|
5
|
+
'Generated by Prodify.',
|
|
6
|
+
`Target agent: ${agent}`,
|
|
7
|
+
`Canonical source: ${renderCanonicalSourceList(canonicalSources)}`,
|
|
8
|
+
`Regenerate with: ${regenerateCommand}`,
|
|
9
|
+
`Body fingerprint: ${bodyFingerprint}`,
|
|
10
|
+
'Manual edits may be overwritten.',
|
|
11
|
+
'-->'
|
|
12
|
+
].join('\n');
|
|
13
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { buildManagedMarkdownOutput, readCanonicalMarkdown } from './shared.js';
|
|
2
|
+
import { LEGACY_TARGET_PATH_DEFINITIONS } from '../core/paths.js';
|
|
3
|
+
export async function generateOpenCodeContent(repoRoot) {
|
|
4
|
+
const target = LEGACY_TARGET_PATH_DEFINITIONS.opencode;
|
|
5
|
+
const guidance = await readCanonicalMarkdown(repoRoot, target.canonicalSources[0]);
|
|
6
|
+
const runtime = await readCanonicalMarkdown(repoRoot, '.prodify/runtime-commands.md');
|
|
7
|
+
const body = `${guidance.trimEnd()}\n\n${runtime.trim()}\n`;
|
|
8
|
+
return buildManagedMarkdownOutput({
|
|
9
|
+
agent: target.agent,
|
|
10
|
+
canonicalSources: target.canonicalSources,
|
|
11
|
+
regenerateCommand: 'prodify update',
|
|
12
|
+
body
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { ProdifyError } from '../core/errors.js';
|
|
3
|
+
import { resolveCanonicalPath } from '../core/paths.js';
|
|
4
|
+
import { computeBodyFingerprint } from '../core/managed-files.js';
|
|
5
|
+
import { renderManagedFileHeader } from './header.js';
|
|
6
|
+
export async function readCanonicalMarkdown(repoRoot, relativePath) {
|
|
7
|
+
const absolutePath = resolveCanonicalPath(repoRoot, relativePath);
|
|
8
|
+
try {
|
|
9
|
+
return await fs.readFile(absolutePath, 'utf8');
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
throw new ProdifyError(`Canonical source is missing: ${relativePath}`, {
|
|
13
|
+
code: 'CANONICAL_SOURCE_MISSING'
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function stripLeadingTitle(markdown) {
|
|
18
|
+
const lines = markdown.split('\n');
|
|
19
|
+
if (lines[0]?.startsWith('# ')) {
|
|
20
|
+
let index = 1;
|
|
21
|
+
while (index < lines.length && lines[index].trim() === '') {
|
|
22
|
+
index += 1;
|
|
23
|
+
}
|
|
24
|
+
return lines.slice(index).join('\n').trim();
|
|
25
|
+
}
|
|
26
|
+
return markdown.trim();
|
|
27
|
+
}
|
|
28
|
+
export function buildManagedMarkdownOutput({ agent, canonicalSources, regenerateCommand, body }) {
|
|
29
|
+
const bodyFingerprint = computeBodyFingerprint(body);
|
|
30
|
+
const header = renderManagedFileHeader({
|
|
31
|
+
agent,
|
|
32
|
+
canonicalSources,
|
|
33
|
+
regenerateCommand,
|
|
34
|
+
bodyFingerprint
|
|
35
|
+
});
|
|
36
|
+
return `${header}\n\n${body}`;
|
|
37
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { ProdifyError } from '../core/errors.js';
|
|
2
|
+
import { LEGACY_TARGET_PATH_DEFINITIONS, isRuntimeProfileName } from '../core/paths.js';
|
|
3
|
+
import { generateCodexContent } from '../generators/codex.js';
|
|
4
|
+
import { generateClaudeContent } from '../generators/claude.js';
|
|
5
|
+
import { generateCopilotContent } from '../generators/copilot.js';
|
|
6
|
+
import { generateOpenCodeContent } from '../generators/opencode.js';
|
|
7
|
+
export const LEGACY_TARGET_REGISTRY = {
|
|
8
|
+
codex: {
|
|
9
|
+
...LEGACY_TARGET_PATH_DEFINITIONS.codex,
|
|
10
|
+
enabled: true,
|
|
11
|
+
doctorEligible: false,
|
|
12
|
+
generator: generateCodexContent
|
|
13
|
+
},
|
|
14
|
+
claude: {
|
|
15
|
+
...LEGACY_TARGET_PATH_DEFINITIONS.claude,
|
|
16
|
+
enabled: true,
|
|
17
|
+
doctorEligible: false,
|
|
18
|
+
generator: generateClaudeContent
|
|
19
|
+
},
|
|
20
|
+
copilot: {
|
|
21
|
+
...LEGACY_TARGET_PATH_DEFINITIONS.copilot,
|
|
22
|
+
enabled: true,
|
|
23
|
+
doctorEligible: false,
|
|
24
|
+
generator: generateCopilotContent
|
|
25
|
+
},
|
|
26
|
+
opencode: {
|
|
27
|
+
...LEGACY_TARGET_PATH_DEFINITIONS.opencode,
|
|
28
|
+
enabled: true,
|
|
29
|
+
doctorEligible: false,
|
|
30
|
+
generator: generateOpenCodeContent
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export function listLegacyTargets() {
|
|
34
|
+
return Object.values(LEGACY_TARGET_REGISTRY);
|
|
35
|
+
}
|
|
36
|
+
export function getLegacyTarget(agent) {
|
|
37
|
+
if (!agent || !isRuntimeProfileName(agent)) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return LEGACY_TARGET_REGISTRY[agent];
|
|
41
|
+
}
|
|
42
|
+
export function assertSupportedInstallTarget(agent) {
|
|
43
|
+
const metadata = getLegacyTarget(agent);
|
|
44
|
+
if (!metadata) {
|
|
45
|
+
throw new ProdifyError(`Unknown target agent: ${agent}`, {
|
|
46
|
+
code: 'UNKNOWN_TARGET'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (!metadata.enabled || !metadata.generator) {
|
|
50
|
+
throw new ProdifyError(`Legacy compatibility target ${agent} is not enabled.`, {
|
|
51
|
+
code: 'TARGET_NOT_ENABLED'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return metadata;
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { listFilesRecursive } from '../core/fs.js';
|
|
4
|
+
import { validatePresetEntries } from '../core/preset-validation.js';
|
|
5
|
+
import { DEFAULT_PRESET_ASSET_DIR } from './default.js';
|
|
6
|
+
import { serializeVersionMetadata } from './version.js';
|
|
7
|
+
export async function loadDefaultPreset() {
|
|
8
|
+
const metadataPath = path.join(DEFAULT_PRESET_ASSET_DIR, 'preset.json');
|
|
9
|
+
const canonicalDir = path.join(DEFAULT_PRESET_ASSET_DIR, 'canonical');
|
|
10
|
+
const rawMetadata = JSON.parse(await fs.readFile(metadataPath, 'utf8'));
|
|
11
|
+
const metadata = {
|
|
12
|
+
name: rawMetadata.name,
|
|
13
|
+
version: rawMetadata.version,
|
|
14
|
+
schemaVersion: rawMetadata.schemaVersion
|
|
15
|
+
};
|
|
16
|
+
const rawFiles = await listFilesRecursive(canonicalDir);
|
|
17
|
+
const entries = [];
|
|
18
|
+
for (const file of rawFiles) {
|
|
19
|
+
entries.push({
|
|
20
|
+
relativePath: `.prodify/${file.relativePath}`,
|
|
21
|
+
content: await fs.readFile(file.fullPath, 'utf8')
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
entries.push({
|
|
25
|
+
relativePath: '.prodify/version.json',
|
|
26
|
+
content: serializeVersionMetadata(metadata)
|
|
27
|
+
});
|
|
28
|
+
entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
29
|
+
validatePresetEntries(entries);
|
|
30
|
+
return {
|
|
31
|
+
metadata,
|
|
32
|
+
entries
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createVersionMetadata(presetMetadata) {
|
|
2
|
+
return {
|
|
3
|
+
schema_version: presetMetadata.schemaVersion,
|
|
4
|
+
preset_name: presetMetadata.name,
|
|
5
|
+
preset_version: presetMetadata.version
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function serializeVersionMetadata(presetMetadata) {
|
|
9
|
+
return `${JSON.stringify(createVersionMetadata(presetMetadata), null, 2)}\n`;
|
|
10
|
+
}
|
|
11
|
+
export function parseVersionMetadata(content) {
|
|
12
|
+
const parsed = JSON.parse(content);
|
|
13
|
+
return {
|
|
14
|
+
schemaVersion: parsed.schema_version,
|
|
15
|
+
presetName: parsed.preset_name,
|
|
16
|
+
presetVersion: parsed.preset_version
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { inspectCompiledContracts } from '../contracts/freshness.js';
|
|
4
|
+
import { ProdifyError } from '../core/errors.js';
|
|
5
|
+
import { listFilesRecursive, pathExists, writeFileEnsuringDir } from '../core/fs.js';
|
|
6
|
+
import { resolveRepoPath } from '../core/paths.js';
|
|
7
|
+
const SCORE_SCHEMA_VERSION = '1';
|
|
8
|
+
function roundScore(value) {
|
|
9
|
+
return Math.round(value * 100) / 100;
|
|
10
|
+
}
|
|
11
|
+
function createMetric(options) {
|
|
12
|
+
const ratio = Math.max(0, Math.min(1, options.ratio));
|
|
13
|
+
const points = roundScore(options.weight * ratio);
|
|
14
|
+
return {
|
|
15
|
+
id: options.id,
|
|
16
|
+
label: options.label,
|
|
17
|
+
tool: options.tool,
|
|
18
|
+
weight: options.weight,
|
|
19
|
+
max_points: options.weight,
|
|
20
|
+
points,
|
|
21
|
+
status: ratio === 1 ? 'pass' : ratio === 0 ? 'fail' : 'partial',
|
|
22
|
+
details: options.details
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async function detectEcosystems(repoRoot) {
|
|
26
|
+
const ecosystems = [];
|
|
27
|
+
if (await pathExists(resolveRepoPath(repoRoot, 'package.json'))) {
|
|
28
|
+
ecosystems.push('typescript-javascript');
|
|
29
|
+
}
|
|
30
|
+
const repoFiles = await listFilesRecursive(repoRoot);
|
|
31
|
+
if (repoFiles.some((file) => file.relativePath.endsWith('.py'))) {
|
|
32
|
+
ecosystems.push('python');
|
|
33
|
+
}
|
|
34
|
+
if (repoFiles.some((file) => file.relativePath.endsWith('.cs') || file.relativePath.endsWith('.csproj') || file.relativePath.endsWith('.sln'))) {
|
|
35
|
+
ecosystems.push('csharp');
|
|
36
|
+
}
|
|
37
|
+
return ecosystems.sort((left, right) => left.localeCompare(right));
|
|
38
|
+
}
|
|
39
|
+
async function buildEcosystemMetrics(repoRoot, ecosystems) {
|
|
40
|
+
const metrics = [];
|
|
41
|
+
const toolOutputs = [];
|
|
42
|
+
if (ecosystems.includes('typescript-javascript')) {
|
|
43
|
+
const packageJsonPath = resolveRepoPath(repoRoot, 'package.json');
|
|
44
|
+
const tsconfigPath = resolveRepoPath(repoRoot, 'tsconfig.json');
|
|
45
|
+
const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
46
|
+
const scripts = packageJson.scripts ?? {};
|
|
47
|
+
const hasBuild = typeof scripts.build === 'string' && scripts.build.length > 0;
|
|
48
|
+
const hasTest = typeof scripts.test === 'string' && scripts.test.length > 0;
|
|
49
|
+
const hasTsconfig = await pathExists(tsconfigPath);
|
|
50
|
+
const score = [hasBuild, hasTest, hasTsconfig].filter(Boolean).length / 3;
|
|
51
|
+
metrics.push(createMetric({
|
|
52
|
+
id: 'typescript-javascript',
|
|
53
|
+
label: 'TypeScript/JavaScript local tooling',
|
|
54
|
+
tool: 'package-json',
|
|
55
|
+
weight: 15,
|
|
56
|
+
ratio: score,
|
|
57
|
+
details: `build=${hasBuild}, test=${hasTest}, tsconfig=${hasTsconfig}`
|
|
58
|
+
}));
|
|
59
|
+
toolOutputs.push({
|
|
60
|
+
adapter: 'typescript-javascript',
|
|
61
|
+
details: {
|
|
62
|
+
build_script: hasBuild,
|
|
63
|
+
test_script: hasTest,
|
|
64
|
+
tsconfig: hasTsconfig
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (ecosystems.includes('python')) {
|
|
69
|
+
const hasPyproject = await pathExists(resolveRepoPath(repoRoot, 'pyproject.toml'));
|
|
70
|
+
const hasRequirements = await pathExists(resolveRepoPath(repoRoot, 'requirements.txt'));
|
|
71
|
+
const ratio = [hasPyproject, hasRequirements].filter(Boolean).length / 2;
|
|
72
|
+
metrics.push(createMetric({
|
|
73
|
+
id: 'python',
|
|
74
|
+
label: 'Python local tooling',
|
|
75
|
+
tool: 'filesystem',
|
|
76
|
+
weight: 7.5,
|
|
77
|
+
ratio,
|
|
78
|
+
details: `pyproject=${hasPyproject}, requirements=${hasRequirements}`
|
|
79
|
+
}));
|
|
80
|
+
toolOutputs.push({
|
|
81
|
+
adapter: 'python',
|
|
82
|
+
details: {
|
|
83
|
+
pyproject: hasPyproject,
|
|
84
|
+
requirements: hasRequirements
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (ecosystems.includes('csharp')) {
|
|
89
|
+
const repoFiles = await listFilesRecursive(repoRoot);
|
|
90
|
+
const hasSolution = repoFiles.some((file) => file.relativePath.endsWith('.sln'));
|
|
91
|
+
const hasProject = repoFiles.some((file) => file.relativePath.endsWith('.csproj'));
|
|
92
|
+
const ratio = [hasSolution, hasProject].filter(Boolean).length / 2;
|
|
93
|
+
metrics.push(createMetric({
|
|
94
|
+
id: 'csharp',
|
|
95
|
+
label: 'C# local tooling',
|
|
96
|
+
tool: 'filesystem',
|
|
97
|
+
weight: 7.5,
|
|
98
|
+
ratio,
|
|
99
|
+
details: `solution=${hasSolution}, project=${hasProject}`
|
|
100
|
+
}));
|
|
101
|
+
toolOutputs.push({
|
|
102
|
+
adapter: 'csharp',
|
|
103
|
+
details: {
|
|
104
|
+
solution: hasSolution,
|
|
105
|
+
project: hasProject
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
metrics,
|
|
111
|
+
toolOutputs
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async function buildRepoHygieneMetric(repoRoot) {
|
|
115
|
+
const requiredSignals = ['README.md', 'LICENSE', 'tests', '.prodify/contracts-src'];
|
|
116
|
+
const available = await Promise.all(requiredSignals.map((relativePath) => pathExists(resolveRepoPath(repoRoot, relativePath))));
|
|
117
|
+
const ratio = available.filter(Boolean).length / requiredSignals.length;
|
|
118
|
+
return {
|
|
119
|
+
metric: createMetric({
|
|
120
|
+
id: 'repo-hygiene',
|
|
121
|
+
label: 'Repository hygiene signals',
|
|
122
|
+
tool: 'filesystem',
|
|
123
|
+
weight: 20,
|
|
124
|
+
ratio,
|
|
125
|
+
details: requiredSignals.map((entry, index) => `${entry}=${available[index]}`).join(', ')
|
|
126
|
+
}),
|
|
127
|
+
toolOutput: {
|
|
128
|
+
adapter: 'filesystem',
|
|
129
|
+
details: Object.fromEntries(requiredSignals.map((entry, index) => [entry, available[index]]))
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function buildRuntimeMetric(runtimeState) {
|
|
134
|
+
const healthyState = runtimeState.runtime.current_state !== 'failed' && runtimeState.runtime.current_state !== 'blocked';
|
|
135
|
+
const ratio = healthyState ? 1 : 0;
|
|
136
|
+
return {
|
|
137
|
+
metric: createMetric({
|
|
138
|
+
id: 'runtime-state',
|
|
139
|
+
label: 'Contract-driven runtime state',
|
|
140
|
+
tool: 'state-json',
|
|
141
|
+
weight: 25,
|
|
142
|
+
ratio,
|
|
143
|
+
details: `current_state=${runtimeState.runtime.current_state}, status=${runtimeState.runtime.status}`
|
|
144
|
+
}),
|
|
145
|
+
toolOutput: {
|
|
146
|
+
adapter: 'state-json',
|
|
147
|
+
details: {
|
|
148
|
+
current_state: runtimeState.runtime.current_state,
|
|
149
|
+
status: runtimeState.runtime.status,
|
|
150
|
+
last_validation_result: runtimeState.runtime.last_validation_result
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function buildValidationMetric(runtimeState) {
|
|
156
|
+
const passed = runtimeState.runtime.last_validation?.passed === true;
|
|
157
|
+
const finalReady = runtimeState.runtime.current_state === 'validate_complete' || runtimeState.runtime.current_state === 'completed';
|
|
158
|
+
const ratio = passed ? (finalReady ? 1 : 0.5) : 0;
|
|
159
|
+
return {
|
|
160
|
+
metric: createMetric({
|
|
161
|
+
id: 'validation-gate',
|
|
162
|
+
label: 'Validated contract completion',
|
|
163
|
+
tool: 'state-json',
|
|
164
|
+
weight: 10,
|
|
165
|
+
ratio,
|
|
166
|
+
details: `passed=${passed}, final_ready=${finalReady}`
|
|
167
|
+
}),
|
|
168
|
+
toolOutput: {
|
|
169
|
+
adapter: 'validation-gate',
|
|
170
|
+
details: {
|
|
171
|
+
passed,
|
|
172
|
+
final_ready: finalReady
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
export async function calculateLocalScore(repoRoot, { kind, runtimeState }) {
|
|
178
|
+
const contractInventory = await inspectCompiledContracts(repoRoot);
|
|
179
|
+
const ecosystems = await detectEcosystems(repoRoot);
|
|
180
|
+
const metrics = [];
|
|
181
|
+
const toolOutputs = [];
|
|
182
|
+
metrics.push(createMetric({
|
|
183
|
+
id: 'contracts',
|
|
184
|
+
label: 'Compiled contract health',
|
|
185
|
+
tool: 'contract-compiler',
|
|
186
|
+
weight: 30,
|
|
187
|
+
ratio: contractInventory.ok ? 1 : Math.max(0, 1 - ((contractInventory.missingCompiledStages.length + contractInventory.staleStages.length) / 6)),
|
|
188
|
+
details: `source=${contractInventory.sourceCount}, compiled=${contractInventory.compiledCount}, stale=${contractInventory.staleStages.length}, missing=${contractInventory.missingCompiledStages.length}`
|
|
189
|
+
}));
|
|
190
|
+
toolOutputs.push({
|
|
191
|
+
adapter: 'contract-compiler',
|
|
192
|
+
details: {
|
|
193
|
+
ok: contractInventory.ok,
|
|
194
|
+
sourceCount: contractInventory.sourceCount,
|
|
195
|
+
compiledCount: contractInventory.compiledCount,
|
|
196
|
+
staleStages: contractInventory.staleStages,
|
|
197
|
+
missingCompiledStages: contractInventory.missingCompiledStages,
|
|
198
|
+
missingSourceStages: contractInventory.missingSourceStages,
|
|
199
|
+
invalidStages: contractInventory.invalidStages
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
const runtimeMetric = buildRuntimeMetric(runtimeState);
|
|
203
|
+
metrics.push(runtimeMetric.metric);
|
|
204
|
+
toolOutputs.push(runtimeMetric.toolOutput);
|
|
205
|
+
const validationMetric = buildValidationMetric(runtimeState);
|
|
206
|
+
metrics.push(validationMetric.metric);
|
|
207
|
+
toolOutputs.push(validationMetric.toolOutput);
|
|
208
|
+
const hygieneMetric = await buildRepoHygieneMetric(repoRoot);
|
|
209
|
+
metrics.push(hygieneMetric.metric);
|
|
210
|
+
toolOutputs.push(hygieneMetric.toolOutput);
|
|
211
|
+
const ecosystemMetrics = await buildEcosystemMetrics(repoRoot, ecosystems);
|
|
212
|
+
metrics.push(...ecosystemMetrics.metrics);
|
|
213
|
+
toolOutputs.push(...ecosystemMetrics.toolOutputs);
|
|
214
|
+
const totalScore = roundScore(metrics.reduce((sum, metric) => sum + metric.points, 0));
|
|
215
|
+
const maxScore = roundScore(metrics.reduce((sum, metric) => sum + metric.max_points, 0));
|
|
216
|
+
if (kind === 'final' && !(runtimeState.runtime.last_validation?.passed && (runtimeState.runtime.current_state === 'validate_complete' || runtimeState.runtime.current_state === 'completed'))) {
|
|
217
|
+
throw new ProdifyError('Final scoring requires a validated runtime state at validate_complete or completed.', {
|
|
218
|
+
code: 'SCORING_STATE_INVALID'
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
snapshot: {
|
|
223
|
+
schema_version: SCORE_SCHEMA_VERSION,
|
|
224
|
+
kind,
|
|
225
|
+
ecosystems,
|
|
226
|
+
total_score: totalScore,
|
|
227
|
+
max_score: maxScore,
|
|
228
|
+
metrics
|
|
229
|
+
},
|
|
230
|
+
toolOutputs
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function serializeJson(value) {
|
|
234
|
+
return `${JSON.stringify(value, null, 2)}\n`;
|
|
235
|
+
}
|
|
236
|
+
export async function writeScoreSnapshot(repoRoot, { kind, runtimeState }) {
|
|
237
|
+
const { snapshot, toolOutputs } = await calculateLocalScore(repoRoot, {
|
|
238
|
+
kind,
|
|
239
|
+
runtimeState
|
|
240
|
+
});
|
|
241
|
+
const metricsDir = resolveRepoPath(repoRoot, '.prodify/metrics');
|
|
242
|
+
await writeFileEnsuringDir(path.join(metricsDir, `${kind}.score.json`), serializeJson(snapshot));
|
|
243
|
+
await writeFileEnsuringDir(path.join(metricsDir, `${kind}.tools.json`), serializeJson({
|
|
244
|
+
schema_version: SCORE_SCHEMA_VERSION,
|
|
245
|
+
kind,
|
|
246
|
+
outputs: toolOutputs
|
|
247
|
+
}));
|
|
248
|
+
return snapshot;
|
|
249
|
+
}
|
|
250
|
+
export async function writeScoreDelta(repoRoot) {
|
|
251
|
+
const metricsDir = resolveRepoPath(repoRoot, '.prodify/metrics');
|
|
252
|
+
const baseline = JSON.parse(await fs.readFile(path.join(metricsDir, 'baseline.score.json'), 'utf8'));
|
|
253
|
+
const final = JSON.parse(await fs.readFile(path.join(metricsDir, 'final.score.json'), 'utf8'));
|
|
254
|
+
const delta = {
|
|
255
|
+
schema_version: SCORE_SCHEMA_VERSION,
|
|
256
|
+
baseline_score: baseline.total_score,
|
|
257
|
+
final_score: final.total_score,
|
|
258
|
+
delta: roundScore(final.total_score - baseline.total_score)
|
|
259
|
+
};
|
|
260
|
+
await writeFileEnsuringDir(path.join(metricsDir, 'delta.json'), serializeJson(delta));
|
|
261
|
+
return delta;
|
|
262
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { pathExists } from '../core/fs.js';
|
|
3
|
+
import { resolveCanonicalPath } from '../core/paths.js';
|
|
4
|
+
import { ProdifyError } from '../core/errors.js';
|
|
5
|
+
import { validateSkillDefinitionShape, validateSkillRegistryManifest } from './schema.js';
|
|
6
|
+
const SKILL_REGISTRY_PATH = '.prodify/skills/registry.json';
|
|
7
|
+
async function readJsonFile(filePath, missingCode, invalidCode) {
|
|
8
|
+
if (!(await pathExists(filePath))) {
|
|
9
|
+
throw new ProdifyError(`Required skill file is missing: ${filePath}.`, {
|
|
10
|
+
code: missingCode
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new ProdifyError(`Skill file is malformed JSON: ${filePath}.`, {
|
|
18
|
+
code: invalidCode
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function readSkillRegistryManifest(repoRoot) {
|
|
23
|
+
const registryPath = resolveCanonicalPath(repoRoot, SKILL_REGISTRY_PATH);
|
|
24
|
+
return validateSkillRegistryManifest(await readJsonFile(registryPath, 'SKILL_REGISTRY_MISSING', 'SKILL_REGISTRY_INVALID'));
|
|
25
|
+
}
|
|
26
|
+
export async function loadSkillRegistry(repoRoot) {
|
|
27
|
+
const manifest = await readSkillRegistryManifest(repoRoot);
|
|
28
|
+
const registry = new Map();
|
|
29
|
+
for (const relativePath of manifest.skills) {
|
|
30
|
+
const fullPath = resolveCanonicalPath(repoRoot, `.prodify/skills/${relativePath}`);
|
|
31
|
+
const definition = validateSkillDefinitionShape(await readJsonFile(fullPath, 'SKILL_DEFINITION_MISSING', 'SKILL_DEFINITION_INVALID'));
|
|
32
|
+
if (registry.has(definition.id)) {
|
|
33
|
+
throw new ProdifyError(`Duplicate skill id detected: ${definition.id}.`, {
|
|
34
|
+
code: 'SKILL_REGISTRY_INVALID'
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
registry.set(definition.id, definition);
|
|
38
|
+
}
|
|
39
|
+
return registry;
|
|
40
|
+
}
|