@wowok/agent-mcp 2.5.2 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/customer/index.d.ts +2 -2
- package/dist/customer/index.js +1 -1
- package/dist/customer/info-puzzle.d.ts +39 -2
- package/dist/customer/info-puzzle.js +370 -29
- package/dist/customer/risk-assessment.js +51 -14
- package/dist/customer/types.d.ts +85 -0
- package/dist/index.js +46 -6
- package/dist/knowledge/guard-design-patterns.js +104 -1
- package/dist/knowledge/guard-ledger.js +3 -3
- package/dist/knowledge/guard-lint.js +3 -3
- package/dist/knowledge/guard-risk.js +23 -15
- package/dist/knowledge/guard-templates.js +174 -2
- package/dist/knowledge/guard-translation.js +3 -3
- package/dist/knowledge/index.d.ts +3 -0
- package/dist/knowledge/index.js +9 -0
- package/dist/knowledge/machine-context.d.ts +1 -1
- package/dist/knowledge/machine-context.js +21 -0
- package/dist/knowledge/machine-ledger.d.ts +1 -0
- package/dist/knowledge/machine-ledger.js +60 -37
- package/dist/knowledge/machine-risk.js +23 -0
- package/dist/knowledge/machine-templates.js +150 -164
- package/dist/knowledge/overrides-loader.d.ts +13 -0
- package/dist/knowledge/overrides-loader.js +67 -0
- package/dist/knowledge/payment-risk.d.ts +4 -0
- package/dist/knowledge/payment-risk.js +67 -0
- package/dist/knowledge/repository-confirm.d.ts +4 -0
- package/dist/knowledge/repository-confirm.js +44 -0
- package/dist/knowledge/repository-risk.d.ts +8 -2
- package/dist/knowledge/repository-risk.js +61 -0
- package/dist/knowledge/repository-translation.js +23 -0
- package/dist/knowledge/reward-templates.d.ts +1 -1
- package/dist/knowledge/reward-templates.js +77 -5
- package/dist/knowledge/scenario-modes.js +22 -5
- package/dist/knowledge/service-templates.js +38 -0
- package/dist/knowledge/treasury-templates.js +42 -0
- package/dist/knowledge/trust-metrics.d.ts +4 -0
- package/dist/knowledge/trust-metrics.js +67 -7
- package/dist/loop-engineering/context-collect.d.ts +7 -0
- package/dist/loop-engineering/context-collect.js +83 -0
- package/dist/loop-engineering/improve.d.ts +29 -0
- package/dist/loop-engineering/improve.js +178 -0
- package/dist/project/context-assembly.d.ts +16 -0
- package/dist/project/context-assembly.js +130 -0
- package/dist/project/deployment-doc.d.ts +115 -0
- package/dist/project/deployment-doc.js +595 -0
- package/dist/project/index.js +37 -1
- package/dist/project/migration.d.ts +22 -0
- package/dist/project/migration.js +125 -0
- package/dist/project/project-store.d.ts +110 -0
- package/dist/project/project-store.js +425 -0
- package/dist/project/rollback-policy.d.ts +45 -0
- package/dist/project/rollback-policy.js +290 -0
- package/dist/project/stage-gate.d.ts +49 -0
- package/dist/project/stage-gate.js +310 -0
- package/dist/schema/call/allocation.d.ts +5 -0
- package/dist/schema/call/arbitration.d.ts +59 -54
- package/dist/schema/call/base.d.ts +3 -0
- package/dist/schema/call/base.js +1 -0
- package/dist/schema/call/bridge.d.ts +24 -0
- package/dist/schema/call/contact.d.ts +5 -0
- package/dist/schema/call/demand.d.ts +53 -48
- package/dist/schema/call/guard.d.ts +15 -0
- package/dist/schema/call/machine.d.ts +10 -0
- package/dist/schema/call/order.d.ts +5 -0
- package/dist/schema/call/payment.d.ts +5 -0
- package/dist/schema/call/permission.d.ts +5 -0
- package/dist/schema/call/personal.d.ts +5 -0
- package/dist/schema/call/progress.d.ts +5 -0
- package/dist/schema/call/proof.d.ts +10 -0
- package/dist/schema/call/repository.d.ts +5 -0
- package/dist/schema/call/reward.d.ts +5 -0
- package/dist/schema/call/service.d.ts +5 -0
- package/dist/schema/call/treasury.d.ts +5 -0
- package/dist/schema/operations.d.ts +286 -76
- package/dist/schema/operations.js +24 -0
- package/dist/schema/project/index.d.ts +2633 -120
- package/dist/schema/project/index.js +622 -2
- package/dist/schema/query/index.d.ts +601 -0
- package/dist/schema/query/index.js +61 -0
- package/dist/schema/trust/index.d.ts +10 -10
- package/dist/schemas/bridge_operation.schema.json +4 -0
- package/dist/schemas/guard2file.schema.json +4 -0
- package/dist/schemas/index.json +1 -1
- package/dist/schemas/machineNode2file.schema.json +4 -0
- package/dist/schemas/onchain_operations.schema.json +20 -0
- package/dist/schemas/onchain_operations_allocation.schema.json +4 -0
- package/dist/schemas/onchain_operations_arbitration.schema.json +4 -0
- package/dist/schemas/onchain_operations_contact.schema.json +4 -0
- package/dist/schemas/onchain_operations_demand.schema.json +4 -0
- package/dist/schemas/onchain_operations_gen_passport.schema.json +8 -0
- package/dist/schemas/onchain_operations_gen_proof.schema.json +8 -0
- package/dist/schemas/onchain_operations_guard.schema.json +4 -0
- package/dist/schemas/onchain_operations_machine.schema.json +4 -0
- package/dist/schemas/onchain_operations_order.schema.json +4 -0
- package/dist/schemas/onchain_operations_payment.schema.json +4 -0
- package/dist/schemas/onchain_operations_permission.schema.json +4 -0
- package/dist/schemas/onchain_operations_personal.schema.json +4 -0
- package/dist/schemas/onchain_operations_progress.schema.json +4 -0
- package/dist/schemas/onchain_operations_proof.schema.json +4 -0
- package/dist/schemas/onchain_operations_repository.schema.json +4 -0
- package/dist/schemas/onchain_operations_reward.schema.json +4 -0
- package/dist/schemas/onchain_operations_service.schema.json +4 -0
- package/dist/schemas/onchain_operations_treasury.schema.json +4 -0
- package/dist/schemas/project_operation.output.json +1798 -0
- package/dist/schemas/project_operation.schema.json +132 -2
- package/dist/schemas/query_toolkit.output.json +407 -1
- package/dist/schemas/query_toolkit.schema.json +132 -0
- package/dist/tools/handlers/project.js +742 -1
- package/dist/tools/handlers/query.js +38 -2
- package/package.json +2 -2
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface MigrationStep {
|
|
2
|
+
from: string;
|
|
3
|
+
to: string;
|
|
4
|
+
description: string;
|
|
5
|
+
migrate: (projectDirPath: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export interface MigrationResult {
|
|
8
|
+
migrated: boolean;
|
|
9
|
+
from_version: string;
|
|
10
|
+
to_version: string;
|
|
11
|
+
steps_executed: string[];
|
|
12
|
+
backup_path?: string;
|
|
13
|
+
needs_recalibration: boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function migrateProject(prefix: string, version: string): MigrationResult;
|
|
17
|
+
export declare function needsMigration(prefix: string, version: string): boolean;
|
|
18
|
+
export declare function listAvailableMigrations(): Array<{
|
|
19
|
+
from: string;
|
|
20
|
+
to: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { copyFileSync, mkdirSync, readdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { CURRENT_SCHEMA_VERSION, projectDir, loadManifest, saveManifest, } from "./project-store.js";
|
|
4
|
+
const MIGRATIONS = [];
|
|
5
|
+
function backupProject(prefix, version) {
|
|
6
|
+
const srcDir = projectDir(prefix, version);
|
|
7
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
8
|
+
const backupDir = join(srcDir, ".backup", timestamp);
|
|
9
|
+
mkdirSync(backupDir, { recursive: true });
|
|
10
|
+
function copyDir(srcRel) {
|
|
11
|
+
const srcAbs = join(srcDir, srcRel);
|
|
12
|
+
const items = readdirSync(srcAbs, { withFileTypes: true });
|
|
13
|
+
for (const item of items) {
|
|
14
|
+
if (srcRel === "" && item.name === ".backup")
|
|
15
|
+
continue;
|
|
16
|
+
const rel = srcRel ? join(srcRel, item.name) : item.name;
|
|
17
|
+
const abs = join(srcDir, rel);
|
|
18
|
+
if (item.isDirectory()) {
|
|
19
|
+
mkdirSync(join(backupDir, rel), { recursive: true });
|
|
20
|
+
copyDir(rel);
|
|
21
|
+
}
|
|
22
|
+
else if (item.isFile()) {
|
|
23
|
+
copyFileSync(abs, join(backupDir, rel));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
copyDir("");
|
|
28
|
+
return backupDir;
|
|
29
|
+
}
|
|
30
|
+
export function migrateProject(prefix, version) {
|
|
31
|
+
const manifest = loadManifest(prefix, version);
|
|
32
|
+
const fromVersion = manifest.schema_version;
|
|
33
|
+
if (fromVersion === CURRENT_SCHEMA_VERSION) {
|
|
34
|
+
return {
|
|
35
|
+
migrated: false,
|
|
36
|
+
from_version: fromVersion,
|
|
37
|
+
to_version: CURRENT_SCHEMA_VERSION,
|
|
38
|
+
steps_executed: [],
|
|
39
|
+
needs_recalibration: manifest.needs_recalibration ?? false,
|
|
40
|
+
message: `Project schema is up-to-date (${fromVersion}). No migration needed.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const backupPath = backupProject(prefix, version);
|
|
44
|
+
const dir = projectDir(prefix, version);
|
|
45
|
+
const stepsExecuted = [];
|
|
46
|
+
let current = fromVersion;
|
|
47
|
+
let safetyCounter = 0;
|
|
48
|
+
const MAX_STEPS = 50;
|
|
49
|
+
while (current !== CURRENT_SCHEMA_VERSION && safetyCounter < MAX_STEPS) {
|
|
50
|
+
const step = MIGRATIONS.find((m) => m.from === current);
|
|
51
|
+
if (!step) {
|
|
52
|
+
const updatedManifest = {
|
|
53
|
+
...manifest,
|
|
54
|
+
needs_recalibration: true,
|
|
55
|
+
updated_at: new Date().toISOString(),
|
|
56
|
+
};
|
|
57
|
+
saveManifest(prefix, version, updatedManifest);
|
|
58
|
+
return {
|
|
59
|
+
migrated: false,
|
|
60
|
+
from_version: fromVersion,
|
|
61
|
+
to_version: current,
|
|
62
|
+
steps_executed: stepsExecuted,
|
|
63
|
+
backup_path: backupPath,
|
|
64
|
+
needs_recalibration: true,
|
|
65
|
+
message: `No migration path from schema ${current} to ${CURRENT_SCHEMA_VERSION}. ` +
|
|
66
|
+
`Project backed up to ${backupPath}. Manual recalibration required.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
step.migrate(dir);
|
|
71
|
+
stepsExecuted.push(`${step.from}→${step.to}: ${step.description}`);
|
|
72
|
+
current = step.to;
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
const updatedManifest = {
|
|
76
|
+
...manifest,
|
|
77
|
+
needs_recalibration: true,
|
|
78
|
+
updated_at: new Date().toISOString(),
|
|
79
|
+
};
|
|
80
|
+
saveManifest(prefix, version, updatedManifest);
|
|
81
|
+
return {
|
|
82
|
+
migrated: false,
|
|
83
|
+
from_version: fromVersion,
|
|
84
|
+
to_version: current,
|
|
85
|
+
steps_executed: stepsExecuted,
|
|
86
|
+
backup_path: backupPath,
|
|
87
|
+
needs_recalibration: true,
|
|
88
|
+
message: `Migration ${step.from}→${step.to} failed: ${e instanceof Error ? e.message : String(e)}. ` +
|
|
89
|
+
`Project backed up to ${backupPath}. Manual intervention required.`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
safetyCounter++;
|
|
93
|
+
}
|
|
94
|
+
const updatedManifest = {
|
|
95
|
+
...manifest,
|
|
96
|
+
schema_version: CURRENT_SCHEMA_VERSION,
|
|
97
|
+
needs_recalibration: true,
|
|
98
|
+
updated_at: new Date().toISOString(),
|
|
99
|
+
};
|
|
100
|
+
saveManifest(prefix, version, updatedManifest);
|
|
101
|
+
return {
|
|
102
|
+
migrated: true,
|
|
103
|
+
from_version: fromVersion,
|
|
104
|
+
to_version: CURRENT_SCHEMA_VERSION,
|
|
105
|
+
steps_executed: stepsExecuted,
|
|
106
|
+
backup_path: backupPath,
|
|
107
|
+
needs_recalibration: true,
|
|
108
|
+
message: `Project migrated from schema ${fromVersion} to ${CURRENT_SCHEMA_VERSION}. ` +
|
|
109
|
+
`${stepsExecuted.length} migration step(s) executed. ` +
|
|
110
|
+
`Recalibration recommended: re-run aggregate_risks and generate_deployment_doc. ` +
|
|
111
|
+
`Backup saved to ${backupPath}.`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function needsMigration(prefix, version) {
|
|
115
|
+
try {
|
|
116
|
+
const manifest = loadManifest(prefix, version);
|
|
117
|
+
return manifest.schema_version !== CURRENT_SCHEMA_VERSION;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export function listAvailableMigrations() {
|
|
124
|
+
return MIGRATIONS.map((m) => ({ from: m.from, to: m.to, description: m.description }));
|
|
125
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export declare const CURRENT_SCHEMA_VERSION = "1.0.0";
|
|
2
|
+
export declare function projectsRootDir(): string;
|
|
3
|
+
export declare function projectDir(prefix: string, version: string): string;
|
|
4
|
+
export declare function deploymentDir(prefix: string, version: string): string;
|
|
5
|
+
export interface ProjectManifest {
|
|
6
|
+
schema_version: string;
|
|
7
|
+
mcp_version: string;
|
|
8
|
+
prefix: string;
|
|
9
|
+
version: string;
|
|
10
|
+
created_at: string;
|
|
11
|
+
updated_at: string;
|
|
12
|
+
forked_from?: string;
|
|
13
|
+
needs_recalibration?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type ProjectStage = 1 | 2 | 3 | 4 | 5;
|
|
16
|
+
export interface StageState {
|
|
17
|
+
stage: ProjectStage;
|
|
18
|
+
updated_at: number;
|
|
19
|
+
stage_meta: Record<ProjectStage, {
|
|
20
|
+
committed: boolean;
|
|
21
|
+
committed_at?: number;
|
|
22
|
+
gate_passed: boolean;
|
|
23
|
+
blocking_issues: string[];
|
|
24
|
+
completion: number;
|
|
25
|
+
}>;
|
|
26
|
+
has_published_object: boolean;
|
|
27
|
+
archived: boolean;
|
|
28
|
+
}
|
|
29
|
+
export type AuditEventType = "stage_commit" | "stage_rollback" | "risk_acknowledged" | "fork_created" | "migration_applied" | "gate_validation" | "object_published";
|
|
30
|
+
export interface AuditRecord {
|
|
31
|
+
timestamp: string;
|
|
32
|
+
event: AuditEventType;
|
|
33
|
+
stage?: ProjectStage;
|
|
34
|
+
reason: string;
|
|
35
|
+
affected_files: string[];
|
|
36
|
+
user_confirmed?: boolean;
|
|
37
|
+
}
|
|
38
|
+
export declare function ensureProjectDir(prefix: string, version: string): string;
|
|
39
|
+
export declare function initProject(prefix: string, version: string, options?: {
|
|
40
|
+
mcpVersion?: string;
|
|
41
|
+
forkedFrom?: string;
|
|
42
|
+
}): ProjectManifest;
|
|
43
|
+
export declare function loadManifest(prefix: string, version: string): ProjectManifest;
|
|
44
|
+
export declare function saveManifest(prefix: string, version: string, manifest: ProjectManifest): void;
|
|
45
|
+
export declare function loadStageState(prefix: string, version: string): StageState | undefined;
|
|
46
|
+
export declare function saveStageState(prefix: string, version: string, state: StageState): void;
|
|
47
|
+
export declare function saveProjectFile(prefix: string, version: string, relPath: string, data: unknown): string;
|
|
48
|
+
export declare function loadProjectFile<T = unknown>(prefix: string, version: string, relPath: string): T | undefined;
|
|
49
|
+
export declare function saveProjectMarkdown(prefix: string, version: string, relPath: string, markdown: string): string;
|
|
50
|
+
export declare function appendJsonl(prefix: string, version: string, relPath: string, record: unknown): string;
|
|
51
|
+
export declare function appendAudit(prefix: string, version: string, record: AuditRecord): void;
|
|
52
|
+
export declare function readAuditLog(prefix: string, version: string): AuditRecord[];
|
|
53
|
+
export interface LocalProjectEntry {
|
|
54
|
+
dir_name: string;
|
|
55
|
+
prefix: string;
|
|
56
|
+
version: string;
|
|
57
|
+
path: string;
|
|
58
|
+
initialized: boolean;
|
|
59
|
+
archived: boolean;
|
|
60
|
+
needs_migration: boolean;
|
|
61
|
+
forked_from?: string;
|
|
62
|
+
mtime: number;
|
|
63
|
+
}
|
|
64
|
+
export declare function parseProjectDirName(dirName: string): {
|
|
65
|
+
prefix: string;
|
|
66
|
+
version: string;
|
|
67
|
+
} | undefined;
|
|
68
|
+
export declare function listLocalProjects(): LocalProjectEntry[];
|
|
69
|
+
export declare function projectExists(prefix: string, version: string): boolean;
|
|
70
|
+
export interface ForkOptions {
|
|
71
|
+
newVersion: string;
|
|
72
|
+
newPrefix?: string;
|
|
73
|
+
mcpVersion?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface ForkResult {
|
|
76
|
+
new_prefix: string;
|
|
77
|
+
new_version: string;
|
|
78
|
+
new_path: string;
|
|
79
|
+
forked_from: string;
|
|
80
|
+
copied_files: string[];
|
|
81
|
+
}
|
|
82
|
+
export declare function forkProject(srcPrefix: string, srcVersion: string, opts: ForkOptions): ForkResult;
|
|
83
|
+
export interface ProjectFileEntry {
|
|
84
|
+
path: string;
|
|
85
|
+
abs_path: string;
|
|
86
|
+
size: number;
|
|
87
|
+
mtime: number;
|
|
88
|
+
kind: "json" | "markdown" | "jsonl" | "other";
|
|
89
|
+
}
|
|
90
|
+
export declare function listProjectFiles(prefix: string, version: string): ProjectFileEntry[];
|
|
91
|
+
export declare function readProjectFile(prefix: string, version: string, relPath: string): string | undefined;
|
|
92
|
+
export declare function commitStage(prefix: string, version: string, stage: ProjectStage, options?: {
|
|
93
|
+
gatePassed?: boolean;
|
|
94
|
+
blockingIssues?: string[];
|
|
95
|
+
completion?: number;
|
|
96
|
+
reason?: string;
|
|
97
|
+
userConfirmed?: boolean;
|
|
98
|
+
}): StageState;
|
|
99
|
+
export declare function rollbackStage(prefix: string, version: string, toStage: ProjectStage, options?: {
|
|
100
|
+
reason?: string;
|
|
101
|
+
affectedFiles?: string[];
|
|
102
|
+
}): StageState;
|
|
103
|
+
export declare function markObjectPublished(prefix: string, version: string, objectName: string): void;
|
|
104
|
+
export interface ProjectSyncStatus {
|
|
105
|
+
local_exists: boolean;
|
|
106
|
+
initialized: boolean;
|
|
107
|
+
needs_migration: boolean;
|
|
108
|
+
archived: boolean;
|
|
109
|
+
}
|
|
110
|
+
export declare function getLocalSyncStatus(prefix: string, version: string): ProjectSyncStatus;
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, copyFileSync, statSync, appendFileSync, } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
export const CURRENT_SCHEMA_VERSION = "1.0.0";
|
|
5
|
+
export function projectsRootDir() {
|
|
6
|
+
const base = process.env.WOWOK_CONFIG_DIR
|
|
7
|
+
? process.env.WOWOK_CONFIG_DIR
|
|
8
|
+
: join(homedir(), ".wowok");
|
|
9
|
+
return join(base, "projects");
|
|
10
|
+
}
|
|
11
|
+
export function projectDir(prefix, version) {
|
|
12
|
+
return join(projectsRootDir(), `${prefix}_${version}`);
|
|
13
|
+
}
|
|
14
|
+
export function deploymentDir(prefix, version) {
|
|
15
|
+
return join(projectDir(prefix, version), "deployment");
|
|
16
|
+
}
|
|
17
|
+
function manifestPath(prefix, version) {
|
|
18
|
+
return join(projectDir(prefix, version), "manifest.json");
|
|
19
|
+
}
|
|
20
|
+
function stageStatePath(prefix, version) {
|
|
21
|
+
return join(projectDir(prefix, version), "stage.json");
|
|
22
|
+
}
|
|
23
|
+
function auditLogPath(prefix, version) {
|
|
24
|
+
return join(projectDir(prefix, version), "audit.jsonl");
|
|
25
|
+
}
|
|
26
|
+
export function ensureProjectDir(prefix, version) {
|
|
27
|
+
const dir = projectDir(prefix, version);
|
|
28
|
+
if (!existsSync(dir)) {
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
export function initProject(prefix, version, options = {}) {
|
|
34
|
+
ensureProjectDir(prefix, version);
|
|
35
|
+
const now = new Date().toISOString();
|
|
36
|
+
const manifest = {
|
|
37
|
+
schema_version: CURRENT_SCHEMA_VERSION,
|
|
38
|
+
mcp_version: options.mcpVersion ?? "unknown",
|
|
39
|
+
prefix,
|
|
40
|
+
version,
|
|
41
|
+
created_at: now,
|
|
42
|
+
updated_at: now,
|
|
43
|
+
forked_from: options.forkedFrom,
|
|
44
|
+
};
|
|
45
|
+
writeFileSync(manifestPath(prefix, version), JSON.stringify(manifest, null, 2), "utf-8");
|
|
46
|
+
const stageState = {
|
|
47
|
+
stage: 1,
|
|
48
|
+
updated_at: Date.now(),
|
|
49
|
+
stage_meta: {
|
|
50
|
+
1: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
51
|
+
2: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
52
|
+
3: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
53
|
+
4: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
54
|
+
5: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
55
|
+
},
|
|
56
|
+
has_published_object: false,
|
|
57
|
+
archived: false,
|
|
58
|
+
};
|
|
59
|
+
saveStageState(prefix, version, stageState);
|
|
60
|
+
appendAudit(prefix, version, {
|
|
61
|
+
timestamp: now,
|
|
62
|
+
event: "stage_commit",
|
|
63
|
+
stage: 1,
|
|
64
|
+
reason: `project initialized${options.forkedFrom ? ` (forked from ${options.forkedFrom})` : ""}`,
|
|
65
|
+
affected_files: ["manifest.json", "stage.json"],
|
|
66
|
+
});
|
|
67
|
+
return manifest;
|
|
68
|
+
}
|
|
69
|
+
export function loadManifest(prefix, version) {
|
|
70
|
+
const p = manifestPath(prefix, version);
|
|
71
|
+
if (!existsSync(p)) {
|
|
72
|
+
throw new Error(`project not initialized: ${prefix}_${version} (missing manifest.json)`);
|
|
73
|
+
}
|
|
74
|
+
const raw = readFileSync(p, "utf-8");
|
|
75
|
+
return JSON.parse(raw);
|
|
76
|
+
}
|
|
77
|
+
export function saveManifest(prefix, version, manifest) {
|
|
78
|
+
manifest.updated_at = new Date().toISOString();
|
|
79
|
+
writeFileSync(manifestPath(prefix, version), JSON.stringify(manifest, null, 2), "utf-8");
|
|
80
|
+
}
|
|
81
|
+
export function loadStageState(prefix, version) {
|
|
82
|
+
const p = stageStatePath(prefix, version);
|
|
83
|
+
if (!existsSync(p))
|
|
84
|
+
return undefined;
|
|
85
|
+
const raw = readFileSync(p, "utf-8");
|
|
86
|
+
return JSON.parse(raw);
|
|
87
|
+
}
|
|
88
|
+
export function saveStageState(prefix, version, state) {
|
|
89
|
+
state.updated_at = Date.now();
|
|
90
|
+
ensureProjectDir(prefix, version);
|
|
91
|
+
writeFileSync(stageStatePath(prefix, version), JSON.stringify(state, null, 2), "utf-8");
|
|
92
|
+
}
|
|
93
|
+
export function saveProjectFile(prefix, version, relPath, data) {
|
|
94
|
+
const abs = join(projectDir(prefix, version), relPath);
|
|
95
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
96
|
+
writeFileSync(abs, JSON.stringify(data, null, 2), "utf-8");
|
|
97
|
+
return abs;
|
|
98
|
+
}
|
|
99
|
+
export function loadProjectFile(prefix, version, relPath) {
|
|
100
|
+
const abs = join(projectDir(prefix, version), relPath);
|
|
101
|
+
if (!existsSync(abs))
|
|
102
|
+
return undefined;
|
|
103
|
+
const raw = readFileSync(abs, "utf-8");
|
|
104
|
+
return JSON.parse(raw);
|
|
105
|
+
}
|
|
106
|
+
export function saveProjectMarkdown(prefix, version, relPath, markdown) {
|
|
107
|
+
const abs = join(projectDir(prefix, version), relPath);
|
|
108
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
109
|
+
writeFileSync(abs, markdown, "utf-8");
|
|
110
|
+
return abs;
|
|
111
|
+
}
|
|
112
|
+
export function appendJsonl(prefix, version, relPath, record) {
|
|
113
|
+
const abs = join(projectDir(prefix, version), relPath);
|
|
114
|
+
ensureProjectDir(prefix, version);
|
|
115
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
116
|
+
appendFileSync(abs, JSON.stringify(record) + "\n", "utf-8");
|
|
117
|
+
return abs;
|
|
118
|
+
}
|
|
119
|
+
export function appendAudit(prefix, version, record) {
|
|
120
|
+
appendJsonl(prefix, version, "audit.jsonl", record);
|
|
121
|
+
}
|
|
122
|
+
export function readAuditLog(prefix, version) {
|
|
123
|
+
const abs = auditLogPath(prefix, version);
|
|
124
|
+
if (!existsSync(abs))
|
|
125
|
+
return [];
|
|
126
|
+
const raw = readFileSync(abs, "utf-8");
|
|
127
|
+
return raw
|
|
128
|
+
.split("\n")
|
|
129
|
+
.filter((l) => l.trim().length > 0)
|
|
130
|
+
.map((l) => JSON.parse(l));
|
|
131
|
+
}
|
|
132
|
+
export function parseProjectDirName(dirName) {
|
|
133
|
+
const m = dirName.match(/^(.+)_v(\d+)$/);
|
|
134
|
+
if (!m)
|
|
135
|
+
return undefined;
|
|
136
|
+
return { prefix: m[1], version: `v${m[2]}` };
|
|
137
|
+
}
|
|
138
|
+
export function listLocalProjects() {
|
|
139
|
+
const root = projectsRootDir();
|
|
140
|
+
if (!existsSync(root))
|
|
141
|
+
return [];
|
|
142
|
+
const entries = readdirSync(root, { withFileTypes: true });
|
|
143
|
+
const out = [];
|
|
144
|
+
for (const e of entries) {
|
|
145
|
+
if (!e.isDirectory())
|
|
146
|
+
continue;
|
|
147
|
+
const parsed = parseProjectDirName(e.name);
|
|
148
|
+
if (!parsed)
|
|
149
|
+
continue;
|
|
150
|
+
const fullPath = join(root, e.name);
|
|
151
|
+
const manifestFile = join(fullPath, "manifest.json");
|
|
152
|
+
const stageFile = join(fullPath, "stage.json");
|
|
153
|
+
let initialized = false;
|
|
154
|
+
let archived = false;
|
|
155
|
+
let needsMigration = false;
|
|
156
|
+
let forkedFrom;
|
|
157
|
+
if (existsSync(manifestFile)) {
|
|
158
|
+
initialized = true;
|
|
159
|
+
try {
|
|
160
|
+
const m = JSON.parse(readFileSync(manifestFile, "utf-8"));
|
|
161
|
+
needsMigration = m.schema_version !== CURRENT_SCHEMA_VERSION;
|
|
162
|
+
forkedFrom = m.forked_from;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (existsSync(stageFile)) {
|
|
168
|
+
try {
|
|
169
|
+
const s = JSON.parse(readFileSync(stageFile, "utf-8"));
|
|
170
|
+
archived = s.archived;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
let mtime = 0;
|
|
176
|
+
try {
|
|
177
|
+
mtime = statSync(fullPath).mtimeMs;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
}
|
|
181
|
+
out.push({
|
|
182
|
+
dir_name: e.name,
|
|
183
|
+
prefix: parsed.prefix,
|
|
184
|
+
version: parsed.version,
|
|
185
|
+
path: fullPath,
|
|
186
|
+
initialized,
|
|
187
|
+
archived,
|
|
188
|
+
needs_migration: needsMigration,
|
|
189
|
+
forked_from: forkedFrom,
|
|
190
|
+
mtime,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return out.sort((a, b) => b.mtime - a.mtime);
|
|
194
|
+
}
|
|
195
|
+
export function projectExists(prefix, version) {
|
|
196
|
+
return existsSync(manifestPath(prefix, version));
|
|
197
|
+
}
|
|
198
|
+
export function forkProject(srcPrefix, srcVersion, opts) {
|
|
199
|
+
if (!projectExists(srcPrefix, srcVersion)) {
|
|
200
|
+
throw new Error(`source project not found: ${srcPrefix}_${srcVersion}`);
|
|
201
|
+
}
|
|
202
|
+
const newPrefix = opts.newPrefix ?? srcPrefix;
|
|
203
|
+
if (opts.newVersion === srcVersion && newPrefix === srcPrefix) {
|
|
204
|
+
throw new Error("fork target must differ from source (set newVersion or newPrefix)");
|
|
205
|
+
}
|
|
206
|
+
ensureProjectDir(newPrefix, opts.newVersion);
|
|
207
|
+
const srcDir = projectDir(srcPrefix, srcVersion);
|
|
208
|
+
const dstDir = projectDir(newPrefix, opts.newVersion);
|
|
209
|
+
const copiedFiles = [];
|
|
210
|
+
function walkDir(relBase) {
|
|
211
|
+
const absBase = join(srcDir, relBase);
|
|
212
|
+
if (!existsSync(absBase))
|
|
213
|
+
return;
|
|
214
|
+
const items = readdirSync(absBase, { withFileTypes: true });
|
|
215
|
+
for (const item of items) {
|
|
216
|
+
const rel = relBase ? join(relBase, item.name) : item.name;
|
|
217
|
+
const srcAbs = join(srcDir, rel);
|
|
218
|
+
const dstAbs = join(dstDir, rel);
|
|
219
|
+
if (item.isDirectory()) {
|
|
220
|
+
mkdirSync(dstAbs, { recursive: true });
|
|
221
|
+
walkDir(rel);
|
|
222
|
+
}
|
|
223
|
+
else if (item.isFile()) {
|
|
224
|
+
if (rel === "stage.json" || rel === "audit.jsonl")
|
|
225
|
+
continue;
|
|
226
|
+
mkdirSync(dirname(dstAbs), { recursive: true });
|
|
227
|
+
copyFileSync(srcAbs, dstAbs);
|
|
228
|
+
copiedFiles.push(rel);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
walkDir("");
|
|
233
|
+
const now = new Date().toISOString();
|
|
234
|
+
const manifest = {
|
|
235
|
+
schema_version: CURRENT_SCHEMA_VERSION,
|
|
236
|
+
mcp_version: opts.mcpVersion ?? "unknown",
|
|
237
|
+
prefix: newPrefix,
|
|
238
|
+
version: opts.newVersion,
|
|
239
|
+
created_at: now,
|
|
240
|
+
updated_at: now,
|
|
241
|
+
forked_from: srcDir,
|
|
242
|
+
};
|
|
243
|
+
writeFileSync(manifestPath(newPrefix, opts.newVersion), JSON.stringify(manifest, null, 2), "utf-8");
|
|
244
|
+
const stageState = {
|
|
245
|
+
stage: 1,
|
|
246
|
+
updated_at: Date.now(),
|
|
247
|
+
stage_meta: {
|
|
248
|
+
1: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
249
|
+
2: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
250
|
+
3: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
251
|
+
4: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
252
|
+
5: { committed: false, gate_passed: false, blocking_issues: [], completion: 0 },
|
|
253
|
+
},
|
|
254
|
+
has_published_object: false,
|
|
255
|
+
archived: false,
|
|
256
|
+
};
|
|
257
|
+
saveStageState(newPrefix, opts.newVersion, stageState);
|
|
258
|
+
appendAudit(newPrefix, opts.newVersion, {
|
|
259
|
+
timestamp: now,
|
|
260
|
+
event: "fork_created",
|
|
261
|
+
reason: `forked from ${srcPrefix}_${srcVersion}; objects must be re-created (project isolation §3.0)`,
|
|
262
|
+
affected_files: ["manifest.json", "stage.json"],
|
|
263
|
+
});
|
|
264
|
+
const srcStage = loadStageState(srcPrefix, srcVersion);
|
|
265
|
+
if (srcStage) {
|
|
266
|
+
srcStage.archived = true;
|
|
267
|
+
saveStageState(srcPrefix, srcVersion, srcStage);
|
|
268
|
+
appendAudit(srcPrefix, srcVersion, {
|
|
269
|
+
timestamp: now,
|
|
270
|
+
event: "stage_commit",
|
|
271
|
+
reason: `archived after fork to ${newPrefix}_${opts.newVersion}`,
|
|
272
|
+
affected_files: ["stage.json"],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
new_prefix: newPrefix,
|
|
277
|
+
new_version: opts.newVersion,
|
|
278
|
+
new_path: dstDir,
|
|
279
|
+
forked_from: srcDir,
|
|
280
|
+
copied_files: copiedFiles,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
export function listProjectFiles(prefix, version) {
|
|
284
|
+
const dir = projectDir(prefix, version);
|
|
285
|
+
if (!existsSync(dir))
|
|
286
|
+
return [];
|
|
287
|
+
const out = [];
|
|
288
|
+
function walk(relBase) {
|
|
289
|
+
const absBase = join(dir, relBase);
|
|
290
|
+
const items = readdirSync(absBase, { withFileTypes: true });
|
|
291
|
+
for (const item of items) {
|
|
292
|
+
const rel = relBase ? join(relBase, item.name) : item.name;
|
|
293
|
+
const abs = join(dir, rel);
|
|
294
|
+
if (item.isDirectory()) {
|
|
295
|
+
walk(rel);
|
|
296
|
+
}
|
|
297
|
+
else if (item.isFile()) {
|
|
298
|
+
let stat;
|
|
299
|
+
try {
|
|
300
|
+
stat = statSync(abs);
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
let kind = "other";
|
|
306
|
+
if (abs.endsWith(".json"))
|
|
307
|
+
kind = "json";
|
|
308
|
+
else if (abs.endsWith(".md"))
|
|
309
|
+
kind = "markdown";
|
|
310
|
+
else if (abs.endsWith(".jsonl"))
|
|
311
|
+
kind = "jsonl";
|
|
312
|
+
out.push({
|
|
313
|
+
path: rel,
|
|
314
|
+
abs_path: abs,
|
|
315
|
+
size: stat.size,
|
|
316
|
+
mtime: stat.mtimeMs,
|
|
317
|
+
kind,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
walk("");
|
|
323
|
+
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
324
|
+
}
|
|
325
|
+
export function readProjectFile(prefix, version, relPath) {
|
|
326
|
+
const abs = join(projectDir(prefix, version), relPath);
|
|
327
|
+
if (!existsSync(abs))
|
|
328
|
+
return undefined;
|
|
329
|
+
return readFileSync(abs, "utf-8");
|
|
330
|
+
}
|
|
331
|
+
export function commitStage(prefix, version, stage, options = {}) {
|
|
332
|
+
const state = loadStageState(prefix, version);
|
|
333
|
+
if (!state) {
|
|
334
|
+
throw new Error(`project not initialized: ${prefix}_${version}`);
|
|
335
|
+
}
|
|
336
|
+
const gatePassed = options.gatePassed ?? false;
|
|
337
|
+
state.stage_meta[stage] = {
|
|
338
|
+
committed: true,
|
|
339
|
+
committed_at: Date.now(),
|
|
340
|
+
gate_passed: gatePassed,
|
|
341
|
+
blocking_issues: options.blockingIssues ?? [],
|
|
342
|
+
completion: options.completion ?? (gatePassed ? 100 : 0),
|
|
343
|
+
};
|
|
344
|
+
if (stage < 5 && gatePassed) {
|
|
345
|
+
state.stage = (stage + 1);
|
|
346
|
+
}
|
|
347
|
+
saveStageState(prefix, version, state);
|
|
348
|
+
appendAudit(prefix, version, {
|
|
349
|
+
timestamp: new Date().toISOString(),
|
|
350
|
+
event: "stage_commit",
|
|
351
|
+
stage,
|
|
352
|
+
reason: options.reason ?? `stage ${stage} committed`,
|
|
353
|
+
affected_files: ["stage.json"],
|
|
354
|
+
user_confirmed: options.userConfirmed,
|
|
355
|
+
});
|
|
356
|
+
return state;
|
|
357
|
+
}
|
|
358
|
+
export function rollbackStage(prefix, version, toStage, options = {}) {
|
|
359
|
+
const state = loadStageState(prefix, version);
|
|
360
|
+
if (!state) {
|
|
361
|
+
throw new Error(`project not initialized: ${prefix}_${version}`);
|
|
362
|
+
}
|
|
363
|
+
if (state.has_published_object) {
|
|
364
|
+
throw new Error(`cannot rollback: project has published objects — must fork new version (§3.3)`);
|
|
365
|
+
}
|
|
366
|
+
const oldStage = state.stage;
|
|
367
|
+
for (let s = toStage + 1; s <= 5; s++) {
|
|
368
|
+
state.stage_meta[s] = {
|
|
369
|
+
committed: false,
|
|
370
|
+
gate_passed: false,
|
|
371
|
+
blocking_issues: [],
|
|
372
|
+
completion: 0,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
state.stage = toStage;
|
|
376
|
+
saveStageState(prefix, version, state);
|
|
377
|
+
appendAudit(prefix, version, {
|
|
378
|
+
timestamp: new Date().toISOString(),
|
|
379
|
+
event: "stage_rollback",
|
|
380
|
+
stage: toStage,
|
|
381
|
+
reason: options.reason ?? `rollback from stage ${oldStage} to ${toStage}`,
|
|
382
|
+
affected_files: options.affectedFiles ?? ["stage.json"],
|
|
383
|
+
});
|
|
384
|
+
return state;
|
|
385
|
+
}
|
|
386
|
+
export function markObjectPublished(prefix, version, objectName) {
|
|
387
|
+
const state = loadStageState(prefix, version);
|
|
388
|
+
if (!state)
|
|
389
|
+
return;
|
|
390
|
+
if (state.has_published_object)
|
|
391
|
+
return;
|
|
392
|
+
state.has_published_object = true;
|
|
393
|
+
saveStageState(prefix, version, state);
|
|
394
|
+
appendAudit(prefix, version, {
|
|
395
|
+
timestamp: new Date().toISOString(),
|
|
396
|
+
event: "object_published",
|
|
397
|
+
stage: 5,
|
|
398
|
+
reason: `object published: ${objectName} — rollback now requires fork (§3.3)`,
|
|
399
|
+
affected_files: ["stage.json"],
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
export function getLocalSyncStatus(prefix, version) {
|
|
403
|
+
const exists = projectExists(prefix, version);
|
|
404
|
+
if (!exists) {
|
|
405
|
+
return { local_exists: false, initialized: false, needs_migration: false, archived: false };
|
|
406
|
+
}
|
|
407
|
+
let initialized = true;
|
|
408
|
+
let needsMigration = false;
|
|
409
|
+
let archived = false;
|
|
410
|
+
try {
|
|
411
|
+
const m = loadManifest(prefix, version);
|
|
412
|
+
needsMigration = m.schema_version !== CURRENT_SCHEMA_VERSION;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
initialized = false;
|
|
416
|
+
}
|
|
417
|
+
try {
|
|
418
|
+
const s = loadStageState(prefix, version);
|
|
419
|
+
if (s)
|
|
420
|
+
archived = s.archived;
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
}
|
|
424
|
+
return { local_exists: true, initialized, needs_migration: needsMigration, archived };
|
|
425
|
+
}
|