pi-harness-runtime 0.10.26 → 0.10.42
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/.pi/plugins/herdr-clipboard/herdr-plugin.toml +22 -0
- package/.pi/plugins/herdr-clipboard/start-bridge.sh +29 -0
- package/.pi/plugins/herdr-clipboard/tmux-watch.sh +39 -0
- package/CHANGELOG.md +41 -0
- package/README.md +87 -1
- package/config.toml.example +35 -0
- package/package.json +6 -4
- package/packages/architecture-generator/dist/adr.d.ts +15 -0
- package/packages/architecture-generator/dist/adr.d.ts.map +1 -0
- package/packages/architecture-generator/dist/adr.js +42 -0
- package/packages/architecture-generator/dist/adr.js.map +1 -0
- package/packages/architecture-generator/dist/index.d.ts +9 -0
- package/packages/architecture-generator/dist/index.d.ts.map +1 -0
- package/packages/architecture-generator/dist/index.js +9 -0
- package/packages/architecture-generator/dist/index.js.map +1 -0
- package/packages/architecture-generator/dist/mermaid.d.ts +30 -0
- package/packages/architecture-generator/dist/mermaid.d.ts.map +1 -0
- package/packages/architecture-generator/dist/mermaid.js +58 -0
- package/packages/architecture-generator/dist/mermaid.js.map +1 -0
- package/packages/architecture-generator/dist/types.d.ts +30 -0
- package/packages/architecture-generator/dist/types.d.ts.map +1 -0
- package/packages/architecture-generator/dist/types.js +7 -0
- package/packages/architecture-generator/dist/types.js.map +1 -0
- package/packages/architecture-generator/src/adr.ts +51 -0
- package/packages/architecture-generator/src/index.ts +9 -0
- package/packages/architecture-generator/src/mermaid.ts +76 -0
- package/packages/architecture-generator/src/types.ts +30 -0
- package/packages/capability-registry/src/types.js +10 -0
- package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
- package/packages/clipboard/dist/src/copy-sync.js +45 -3
- package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
- package/packages/clipboard/dist/src/index.d.ts +1 -0
- package/packages/clipboard/dist/src/index.d.ts.map +1 -1
- package/packages/clipboard/dist/src/index.js +1 -0
- package/packages/clipboard/dist/src/index.js.map +1 -1
- package/packages/clipboard/dist/src/paste-bridge.d.ts +19 -0
- package/packages/clipboard/dist/src/paste-bridge.d.ts.map +1 -0
- package/packages/clipboard/dist/src/paste-bridge.js +78 -0
- package/packages/clipboard/dist/src/paste-bridge.js.map +1 -0
- package/packages/clipboard/src/copy-sync.ts +52 -3
- package/packages/clipboard/src/index.ts +1 -0
- package/packages/clipboard/src/paste-bridge.ts +86 -0
- package/packages/clipboard-plugin/dist/index.d.ts +28 -0
- package/packages/clipboard-plugin/dist/index.d.ts.map +1 -0
- package/packages/clipboard-plugin/dist/index.js +64 -0
- package/packages/clipboard-plugin/dist/index.js.map +1 -0
- package/packages/clipboard-plugin/dist/types.d.ts +30 -0
- package/packages/clipboard-plugin/dist/types.d.ts.map +1 -0
- package/packages/clipboard-plugin/dist/types.js +5 -0
- package/packages/clipboard-plugin/dist/types.js.map +1 -0
- package/packages/clipboard-plugin/src/index.ts +74 -0
- package/packages/clipboard-plugin/src/types.ts +30 -0
- package/packages/file-copy-helper/dist/src/extension.js +84 -25
- package/packages/file-copy-helper/src/extension.debug.ts +226 -0
- package/packages/file-copy-helper/src/extension.ts +89 -59
- package/packages/knowledge-graph/dist/graph-extractor.d.ts +27 -0
- package/packages/knowledge-graph/dist/graph-extractor.d.ts.map +1 -0
- package/packages/knowledge-graph/dist/graph-extractor.js +276 -0
- package/packages/knowledge-graph/dist/graph-extractor.js.map +1 -0
- package/packages/knowledge-graph/dist/index.d.ts +27 -0
- package/packages/knowledge-graph/dist/index.d.ts.map +1 -0
- package/packages/knowledge-graph/dist/index.js +27 -0
- package/packages/knowledge-graph/dist/index.js.map +1 -0
- package/packages/knowledge-graph/dist/mcp-client.d.ts +38 -0
- package/packages/knowledge-graph/dist/mcp-client.d.ts.map +1 -0
- package/packages/knowledge-graph/dist/mcp-client.js +157 -0
- package/packages/knowledge-graph/dist/mcp-client.js.map +1 -0
- package/packages/knowledge-graph/dist/provenance-linker.d.ts +19 -0
- package/packages/knowledge-graph/dist/provenance-linker.d.ts.map +1 -0
- package/packages/knowledge-graph/dist/provenance-linker.js +91 -0
- package/packages/knowledge-graph/dist/provenance-linker.js.map +1 -0
- package/packages/knowledge-graph/dist/types.d.ts +53 -0
- package/packages/knowledge-graph/dist/types.d.ts.map +1 -0
- package/packages/knowledge-graph/dist/types.js +7 -0
- package/packages/knowledge-graph/dist/types.js.map +1 -0
- package/packages/knowledge-graph/src/graph-extractor.ts +338 -0
- package/packages/knowledge-graph/src/index.ts +27 -0
- package/packages/knowledge-graph/src/mcp-client.ts +237 -0
- package/packages/knowledge-graph/src/provenance-linker.ts +101 -0
- package/packages/knowledge-graph/src/types.ts +86 -0
- package/packages/milestone-manager/dist/engine.d.ts +27 -0
- package/packages/milestone-manager/dist/engine.d.ts.map +1 -0
- package/packages/milestone-manager/dist/engine.js +78 -0
- package/packages/milestone-manager/dist/engine.js.map +1 -0
- package/packages/milestone-manager/dist/index.d.ts +6 -0
- package/packages/milestone-manager/dist/index.d.ts.map +1 -0
- package/packages/milestone-manager/dist/index.js +6 -0
- package/packages/milestone-manager/dist/index.js.map +1 -0
- package/packages/milestone-manager/dist/types.d.ts +25 -0
- package/packages/milestone-manager/dist/types.d.ts.map +1 -0
- package/packages/milestone-manager/dist/types.js +7 -0
- package/packages/milestone-manager/dist/types.js.map +1 -0
- package/packages/milestone-manager/src/engine.ts +105 -0
- package/packages/milestone-manager/src/index.ts +6 -0
- package/packages/milestone-manager/src/types.ts +28 -0
- package/packages/notification/dist/adapters/line-adapter.d.ts +17 -0
- package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -0
- package/packages/notification/dist/adapters/line-adapter.js +99 -0
- package/packages/notification/dist/adapters/line-adapter.js.map +1 -0
- package/packages/notification/dist/index.d.ts +1 -0
- package/packages/notification/dist/index.d.ts.map +1 -1
- package/packages/notification/dist/index.js +1 -0
- package/packages/notification/dist/index.js.map +1 -1
- package/packages/notification/dist/notification-center.d.ts.map +1 -1
- package/packages/notification/dist/notification-center.js +4 -0
- package/packages/notification/dist/notification-center.js.map +1 -1
- package/packages/notification/dist/types.d.ts +6 -2
- package/packages/notification/dist/types.d.ts.map +1 -1
- package/packages/notification/src/adapters/line-adapter.ts +119 -0
- package/packages/notification/src/index.ts +1 -0
- package/packages/notification/src/notification-center.ts +6 -0
- package/packages/notification/src/types.ts +7 -2
- package/packages/project-bootstrap/dist/bootstrap.d.ts +23 -0
- package/packages/project-bootstrap/dist/bootstrap.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/bootstrap.js +128 -0
- package/packages/project-bootstrap/dist/bootstrap.js.map +1 -0
- package/packages/project-bootstrap/dist/frappe.d.ts +8 -0
- package/packages/project-bootstrap/dist/frappe.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/frappe.js +82 -0
- package/packages/project-bootstrap/dist/frappe.js.map +1 -0
- package/packages/project-bootstrap/dist/index.d.ts +27 -0
- package/packages/project-bootstrap/dist/index.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/index.js +27 -0
- package/packages/project-bootstrap/dist/index.js.map +1 -0
- package/packages/project-bootstrap/dist/minimal.d.ts +8 -0
- package/packages/project-bootstrap/dist/minimal.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/minimal.js +35 -0
- package/packages/project-bootstrap/dist/minimal.js.map +1 -0
- package/packages/project-bootstrap/dist/types.d.ts +61 -0
- package/packages/project-bootstrap/dist/types.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/types.js +7 -0
- package/packages/project-bootstrap/dist/types.js.map +1 -0
- package/packages/project-bootstrap/dist/typescript.d.ts +8 -0
- package/packages/project-bootstrap/dist/typescript.d.ts.map +1 -0
- package/packages/project-bootstrap/dist/typescript.js +103 -0
- package/packages/project-bootstrap/dist/typescript.js.map +1 -0
- package/packages/project-bootstrap/src/bootstrap.ts +166 -0
- package/packages/project-bootstrap/src/frappe.ts +97 -0
- package/packages/project-bootstrap/src/index.ts +27 -0
- package/packages/project-bootstrap/src/minimal.ts +45 -0
- package/packages/project-bootstrap/src/types.ts +66 -0
- package/packages/project-bootstrap/src/typescript.ts +114 -0
- package/packages/provider-router/dist/src/types.js +1 -1
- package/packages/write-review/src/index.ts +47 -0
- package/packages/write-review/src/injection.ts +21 -1
- package/prompts/agents/reviewer.md +57 -0
- package/prompts/agents/writer.md +42 -0
- package/prompts/slash-commands.md +40 -0
- package/prompts/write-review.md +50 -0
- package/scripts/copy-plugins.js +56 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Main bootstrap orchestration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ProjectSpec,
|
|
9
|
+
BootstrapOptions,
|
|
10
|
+
BootstrapResult,
|
|
11
|
+
ValidationError,
|
|
12
|
+
Template,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
import { minimalTemplate } from './minimal.js';
|
|
15
|
+
import { nodeTypescriptTemplate } from './typescript.js';
|
|
16
|
+
import { frappeTemplate } from './frappe.js';
|
|
17
|
+
|
|
18
|
+
// Built-in templates
|
|
19
|
+
const TEMPLATES: Record<string, Template> = {
|
|
20
|
+
minimal: minimalTemplate,
|
|
21
|
+
'node-typescript': nodeTypescriptTemplate,
|
|
22
|
+
'frappe-app': frappeTemplate,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Validate a project spec
|
|
27
|
+
*/
|
|
28
|
+
export function validateSpec(spec: ProjectSpec): ValidationError[] {
|
|
29
|
+
const errors: ValidationError[] = [];
|
|
30
|
+
|
|
31
|
+
if (!spec.name || spec.name.trim() === '') {
|
|
32
|
+
errors.push({ field: 'name', message: 'Name is required' });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!spec.type || !['monorepo', 'single-package', 'multi-package'].includes(spec.type)) {
|
|
36
|
+
errors.push({ field: 'type', message: 'Type must be monorepo, single-package, or multi-package' });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!spec.frameworks || spec.frameworks.length === 0) {
|
|
40
|
+
errors.push({ field: 'frameworks', message: 'At least one framework is required' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get template for a spec
|
|
48
|
+
*/
|
|
49
|
+
export function getTemplateForSpec(spec: ProjectSpec, override?: string): Template {
|
|
50
|
+
if (override && TEMPLATES[override]) {
|
|
51
|
+
return TEMPLATES[override];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Auto-detect based on frameworks
|
|
55
|
+
if (spec.frameworks.includes('frappe')) {
|
|
56
|
+
return TEMPLATES['frappe-app'];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (spec.frameworks.includes('node') || spec.frameworks.includes('typescript')) {
|
|
60
|
+
return TEMPLATES['node-typescript'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return TEMPLATES['minimal'];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Bootstrap a project from a spec
|
|
68
|
+
*/
|
|
69
|
+
export async function bootstrap(
|
|
70
|
+
spec: ProjectSpec,
|
|
71
|
+
targetDir: string,
|
|
72
|
+
options: BootstrapOptions = {}
|
|
73
|
+
): Promise<BootstrapResult> {
|
|
74
|
+
const startTime = Date.now();
|
|
75
|
+
const errors = validateSpec(spec);
|
|
76
|
+
|
|
77
|
+
if (errors.length > 0) {
|
|
78
|
+
throw new Error(`Invalid spec: ${errors.map(e => e.message).join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { mkdir, writeFile } = await import('fs/promises');
|
|
82
|
+
const { join } = await import('path');
|
|
83
|
+
|
|
84
|
+
// Create target directory
|
|
85
|
+
await mkdir(targetDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
// Get template
|
|
88
|
+
const template = getTemplateForSpec(spec, options.template);
|
|
89
|
+
|
|
90
|
+
// Emit started event
|
|
91
|
+
if (options.verbose) {
|
|
92
|
+
console.log(`Bootstrap: ${spec.name} using template: ${template.name}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Apply template
|
|
96
|
+
const files = await template.apply(spec, targetDir);
|
|
97
|
+
|
|
98
|
+
// Create gitignore if gitInit
|
|
99
|
+
if (spec.gitInit) {
|
|
100
|
+
const gitignore = `node_modules/\ndist/\n.env\n*.log\n`;
|
|
101
|
+
await writeFile(join(targetDir, '.gitignore'), gitignore);
|
|
102
|
+
files.push('.gitignore');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result: BootstrapResult = {
|
|
106
|
+
root: targetDir,
|
|
107
|
+
files,
|
|
108
|
+
packages: spec.packages?.map(p => p.name) || [],
|
|
109
|
+
duration: Date.now() - startTime,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Detect framework and suggest a spec
|
|
117
|
+
*/
|
|
118
|
+
export async function detectAndSuggest(
|
|
119
|
+
projectRoot: string
|
|
120
|
+
): Promise<ProjectSpec | null> {
|
|
121
|
+
const { readdir } = await import('fs/promises');
|
|
122
|
+
const { join } = await import('path');
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const entries = await readdir(projectRoot, { withFileTypes: true });
|
|
126
|
+
const files = entries.filter(e => e.isFile()).map(e => e.name);
|
|
127
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
128
|
+
|
|
129
|
+
// Detect Frappe
|
|
130
|
+
if (dirs.includes('sites') && dirs.includes('apps')) {
|
|
131
|
+
const appName = join(projectRoot).split('/').pop() || 'frappe-app';
|
|
132
|
+
return {
|
|
133
|
+
name: appName,
|
|
134
|
+
type: 'single-package',
|
|
135
|
+
frameworks: ['frappe'],
|
|
136
|
+
gitInit: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Detect Next.js
|
|
141
|
+
if (files.includes('next.config.js') || files.includes('next.config.mjs')) {
|
|
142
|
+
return {
|
|
143
|
+
name: 'nextjs-app',
|
|
144
|
+
type: 'single-package',
|
|
145
|
+
frameworks: ['nextjs'],
|
|
146
|
+
features: { typescript: true, testing: true },
|
|
147
|
+
gitInit: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Detect Node TypeScript
|
|
152
|
+
if (files.includes('package.json') && files.includes('tsconfig.json')) {
|
|
153
|
+
return {
|
|
154
|
+
name: 'node-app',
|
|
155
|
+
type: 'single-package',
|
|
156
|
+
frameworks: ['node', 'typescript'],
|
|
157
|
+
features: { typescript: true, testing: true, eslint: true },
|
|
158
|
+
gitInit: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
} catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Frappe app template
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ProjectSpec, Template } from './types.js';
|
|
8
|
+
|
|
9
|
+
export const frappeTemplate: Template = {
|
|
10
|
+
name: 'frappe-app',
|
|
11
|
+
description: 'Frappe app with modules, doctypes, web pages',
|
|
12
|
+
async apply(spec: ProjectSpec, root: string): Promise<string[]> {
|
|
13
|
+
const { writeFile, mkdir } = await import('fs/promises');
|
|
14
|
+
const { join } = await import('path');
|
|
15
|
+
|
|
16
|
+
const files: string[] = [];
|
|
17
|
+
const appName = spec.name.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
18
|
+
|
|
19
|
+
// MANIFEST.json
|
|
20
|
+
const manifest = {
|
|
21
|
+
name: appName,
|
|
22
|
+
app_name: appName,
|
|
23
|
+
app_title: spec.description || spec.name,
|
|
24
|
+
app_publisher: spec.author || 'Author',
|
|
25
|
+
app_email: 'author@example.com',
|
|
26
|
+
app_description: spec.description || '',
|
|
27
|
+
app_version: '0.0.1',
|
|
28
|
+
app_license: spec.license || 'MIT',
|
|
29
|
+
hooks: {},
|
|
30
|
+
doctypes: [],
|
|
31
|
+
fixtures: [],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
await writeFile(join(root, 'MANIFEST.json'), JSON.stringify(manifest, null, 2));
|
|
35
|
+
files.push('MANIFEST.json');
|
|
36
|
+
|
|
37
|
+
// pyproject.toml
|
|
38
|
+
const pyproject = `[build-system]
|
|
39
|
+
requires = ["flit_core >=3.2,<4"]
|
|
40
|
+
build-backend = "flit_core.buildapi"
|
|
41
|
+
|
|
42
|
+
[project]
|
|
43
|
+
name = "${appName}"
|
|
44
|
+
version = "0.0.1"
|
|
45
|
+
description = "${spec.description || ''}"
|
|
46
|
+
authors = [{name = "${spec.author || 'Author'}", email = "author@example.com"}]
|
|
47
|
+
license = {text = "${spec.license || 'MIT'}"}
|
|
48
|
+
|
|
49
|
+
[tool.frappe]
|
|
50
|
+
app_title = "${spec.name}"
|
|
51
|
+
app_publisher = "${spec.author || 'Author'}"
|
|
52
|
+
app_email = "author@example.com"
|
|
53
|
+
app_description = "${spec.description || ''}"
|
|
54
|
+
app_version = "0.0.1"
|
|
55
|
+
app_license = "${spec.license || 'MIT'}"
|
|
56
|
+
app_modules = []
|
|
57
|
+
`;
|
|
58
|
+
await writeFile(join(root, 'pyproject.toml'), pyproject);
|
|
59
|
+
files.push('pyproject.toml');
|
|
60
|
+
|
|
61
|
+
// app/__init__.py
|
|
62
|
+
await mkdir(join(root, appName), { recursive: true });
|
|
63
|
+
await writeFile(join(root, appName, '__init__.py'), `__version__ = "0.0.1"\n`);
|
|
64
|
+
files.push(`${appName}/__init__.py`);
|
|
65
|
+
|
|
66
|
+
// hooks.py
|
|
67
|
+
const hooksPy = [
|
|
68
|
+
'# App hooks',
|
|
69
|
+
`app_name = "${appName}"`,
|
|
70
|
+
`app_title = "${spec.name}"`,
|
|
71
|
+
`app_publisher = "${spec.author || 'Author'}"`,
|
|
72
|
+
'app_email = "author@example.com"',
|
|
73
|
+
`app_description = "${spec.description || ''}"`,
|
|
74
|
+
'app_version = "0.0.1"',
|
|
75
|
+
`app_license = "${spec.license || 'MIT'}"`,
|
|
76
|
+
'',
|
|
77
|
+
'# hooks',
|
|
78
|
+
'doc_events = {}',
|
|
79
|
+
].join('\n');
|
|
80
|
+
|
|
81
|
+
await writeFile(join(root, appName, 'hooks.py'), hooksPy);
|
|
82
|
+
files.push(`${appName}/hooks.py`);
|
|
83
|
+
|
|
84
|
+
// modules.txt (required for Frappe)
|
|
85
|
+
await writeFile(join(root, 'modules.txt'), '');
|
|
86
|
+
files.push('modules.txt');
|
|
87
|
+
|
|
88
|
+
// README
|
|
89
|
+
await writeFile(
|
|
90
|
+
join(root, 'README.md'),
|
|
91
|
+
`# ${spec.name}\n\n${spec.description || 'A Frappe app.'}\n`
|
|
92
|
+
);
|
|
93
|
+
files.push('README.md');
|
|
94
|
+
|
|
95
|
+
return files;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Create project scaffolds from typed ProjectSpec.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { bootstrap, detectAndSuggest } from '@pi-harness/project-bootstrap';
|
|
9
|
+
*
|
|
10
|
+
* // Bootstrap a new project
|
|
11
|
+
* const result = await bootstrap({
|
|
12
|
+
* name: 'my-app',
|
|
13
|
+
* type: 'single-package',
|
|
14
|
+
* frameworks: ['nextjs'],
|
|
15
|
+
* features: { typescript: true, testing: true }
|
|
16
|
+
* }, '/path/to/create');
|
|
17
|
+
*
|
|
18
|
+
* // Auto-detect framework and suggest spec
|
|
19
|
+
* const spec = await detectAndSuggest('/path/to/existing');
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export * from './types.js';
|
|
24
|
+
export * from './bootstrap.js';
|
|
25
|
+
export { minimalTemplate } from './minimal.js';
|
|
26
|
+
export { nodeTypescriptTemplate } from './typescript.js';
|
|
27
|
+
export { frappeTemplate } from './frappe.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Minimal template - bare project with package.json
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ProjectSpec, Template } from './types.js';
|
|
8
|
+
|
|
9
|
+
export const minimalTemplate: Template = {
|
|
10
|
+
name: 'minimal',
|
|
11
|
+
description: 'Bare project with package.json',
|
|
12
|
+
async apply(spec: ProjectSpec, root: string): Promise<string[]> {
|
|
13
|
+
const { writeFile, mkdir } = await import('fs/promises');
|
|
14
|
+
const { join } = await import('path');
|
|
15
|
+
|
|
16
|
+
const files: string[] = [];
|
|
17
|
+
|
|
18
|
+
// package.json
|
|
19
|
+
const packageJson = {
|
|
20
|
+
name: spec.name,
|
|
21
|
+
version: '0.1.0',
|
|
22
|
+
description: spec.description || '',
|
|
23
|
+
author: spec.author,
|
|
24
|
+
license: spec.license || 'MIT',
|
|
25
|
+
private: spec.type === 'monorepo',
|
|
26
|
+
scripts: {
|
|
27
|
+
build: 'echo "No build script"',
|
|
28
|
+
test: 'echo "No tests"',
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
await writeFile(
|
|
33
|
+
join(root, 'package.json'),
|
|
34
|
+
JSON.stringify(packageJson, null, 2) + '\n'
|
|
35
|
+
);
|
|
36
|
+
files.push('package.json');
|
|
37
|
+
|
|
38
|
+
// README.md
|
|
39
|
+
const readme = `# ${spec.name}\n\n${spec.description || 'A project.'}\n`;
|
|
40
|
+
await writeFile(join(root, 'README.md'), readme);
|
|
41
|
+
files.push('README.md');
|
|
42
|
+
|
|
43
|
+
return files;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Typed definitions for project scaffolding.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type ProjectType = 'monorepo' | 'single-package' | 'multi-package';
|
|
8
|
+
|
|
9
|
+
export interface ProjectSpec {
|
|
10
|
+
name: string;
|
|
11
|
+
type: ProjectType;
|
|
12
|
+
description?: string;
|
|
13
|
+
author?: string;
|
|
14
|
+
license?: string;
|
|
15
|
+
frameworks: string[]; // e.g. ["frappe", "nextjs"]
|
|
16
|
+
packages?: { name: string; type: string }[];
|
|
17
|
+
features?: {
|
|
18
|
+
ci?: boolean;
|
|
19
|
+
cd?: boolean;
|
|
20
|
+
testing?: boolean;
|
|
21
|
+
typescript?: boolean;
|
|
22
|
+
eslint?: boolean;
|
|
23
|
+
prettier?: boolean;
|
|
24
|
+
docs?: boolean;
|
|
25
|
+
};
|
|
26
|
+
gitInit?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BootstrapOptions {
|
|
30
|
+
dryRun?: boolean; // Don't actually create files
|
|
31
|
+
force?: boolean; // Overwrite existing files
|
|
32
|
+
targetDir?: string; // Default: current directory
|
|
33
|
+
template?: string; // Override auto-detected template
|
|
34
|
+
verbose?: boolean; // Log progress
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BootstrapResult {
|
|
38
|
+
root: string;
|
|
39
|
+
files: string[];
|
|
40
|
+
packages: string[];
|
|
41
|
+
duration: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ValidationError {
|
|
45
|
+
field: string;
|
|
46
|
+
message: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Template types
|
|
50
|
+
export interface Template {
|
|
51
|
+
name: string;
|
|
52
|
+
description: string;
|
|
53
|
+
apply: (spec: ProjectSpec, root: string) => Promise<string[]>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface BootstrapContext {
|
|
57
|
+
spec: ProjectSpec;
|
|
58
|
+
root: string;
|
|
59
|
+
files: string[];
|
|
60
|
+
events: EventEmitter;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface EventEmitter {
|
|
64
|
+
emit(event: string, data: unknown): void;
|
|
65
|
+
on(event: string, handler: (data: unknown) => void): void;
|
|
66
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Bootstrap - RFC-0072
|
|
3
|
+
*
|
|
4
|
+
* Node.js + TypeScript template
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ProjectSpec, Template } from './types.js';
|
|
8
|
+
|
|
9
|
+
export const nodeTypescriptTemplate: Template = {
|
|
10
|
+
name: 'node-typescript',
|
|
11
|
+
description: 'Node.js + TypeScript + ESLint + Jest',
|
|
12
|
+
async apply(spec: ProjectSpec, root: string): Promise<string[]> {
|
|
13
|
+
const { writeFile, mkdir } = await import('fs/promises');
|
|
14
|
+
const { join } = await import('path');
|
|
15
|
+
|
|
16
|
+
const files: string[] = [];
|
|
17
|
+
|
|
18
|
+
// package.json
|
|
19
|
+
const packageJson = {
|
|
20
|
+
name: spec.name,
|
|
21
|
+
version: '0.1.0',
|
|
22
|
+
description: spec.description || '',
|
|
23
|
+
type: 'module',
|
|
24
|
+
scripts: {
|
|
25
|
+
build: 'tsc',
|
|
26
|
+
test: 'jest',
|
|
27
|
+
lint: 'eslint src --ext .ts',
|
|
28
|
+
'typecheck': 'tsc --noEmit',
|
|
29
|
+
},
|
|
30
|
+
dependencies: {},
|
|
31
|
+
devDependencies: {
|
|
32
|
+
typescript: '^5.4.0',
|
|
33
|
+
'@types/node': '^20.0.0',
|
|
34
|
+
jest: '^29.7.0',
|
|
35
|
+
'@types/jest': '^29.5.0',
|
|
36
|
+
tsj: '^4.0.0',
|
|
37
|
+
eslint: '^8.57.0',
|
|
38
|
+
'@typescript-eslint/parser': '^6.0.0',
|
|
39
|
+
'@typescript-eslint/eslint-plugin': '^6.0.0',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await writeFile(join(root, 'package.json'), JSON.stringify(packageJson, null, 2) + '\n');
|
|
44
|
+
files.push('package.json');
|
|
45
|
+
|
|
46
|
+
// tsconfig.json
|
|
47
|
+
const tsconfig = {
|
|
48
|
+
compilerOptions: {
|
|
49
|
+
target: 'ES2022',
|
|
50
|
+
module: 'ESNext',
|
|
51
|
+
moduleResolution: 'bundler',
|
|
52
|
+
lib: ['ES2022'],
|
|
53
|
+
outDir: './dist',
|
|
54
|
+
rootDir: './src',
|
|
55
|
+
strict: true,
|
|
56
|
+
esModuleInterop: true,
|
|
57
|
+
skipLibCheck: true,
|
|
58
|
+
forceConsistentCasingInFileNames: true,
|
|
59
|
+
declaration: true,
|
|
60
|
+
declarationMap: true,
|
|
61
|
+
sourceMap: true,
|
|
62
|
+
},
|
|
63
|
+
include: ['src/**/*'],
|
|
64
|
+
exclude: ['node_modules', 'dist'],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
await writeFile(join(root, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2) + '\n');
|
|
68
|
+
files.push('tsconfig.json');
|
|
69
|
+
|
|
70
|
+
// jest.config.js
|
|
71
|
+
const jestConfig = `export default {
|
|
72
|
+
preset: 'tsj',
|
|
73
|
+
testEnvironment: 'node',
|
|
74
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
75
|
+
moduleNameMapper: {
|
|
76
|
+
'^(\\\\.{1,2}/.*)\\.js$': '$1',
|
|
77
|
+
},
|
|
78
|
+
transform: {
|
|
79
|
+
'^.+\\.ts$': ['tsj', { useESM: true }],
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
`;
|
|
83
|
+
await writeFile(join(root, 'jest.config.js'), jestConfig);
|
|
84
|
+
files.push('jest.config.js');
|
|
85
|
+
|
|
86
|
+
// src directory
|
|
87
|
+
await mkdir(join(root, 'src'), { recursive: true });
|
|
88
|
+
await writeFile(join(root, 'src/index.ts'), 'export const main = () => {\n console.log("Hello!");\n};\n');
|
|
89
|
+
files.push('src/index.ts');
|
|
90
|
+
|
|
91
|
+
// .eslintrc.cjs
|
|
92
|
+
const eslintConfig = `module.exports = {
|
|
93
|
+
parser: '@typescript-eslint/parser',
|
|
94
|
+
plugins: ['@typescript-eslint'],
|
|
95
|
+
extends: [
|
|
96
|
+
'eslint:recommended',
|
|
97
|
+
'plugin:@typescript-eslint/recommended',
|
|
98
|
+
],
|
|
99
|
+
root: true,
|
|
100
|
+
env: {
|
|
101
|
+
node: true,
|
|
102
|
+
es2022: true,
|
|
103
|
+
},
|
|
104
|
+
parserOptions: {
|
|
105
|
+
ecmaVersion: 'latest',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
`;
|
|
109
|
+
await writeFile(join(root, '.eslintrc.cjs'), eslintConfig);
|
|
110
|
+
files.push('.eslintrc.cjs');
|
|
111
|
+
|
|
112
|
+
return files;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
@@ -9,6 +9,53 @@
|
|
|
9
9
|
* Directory structure: {project}/.write-review/
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { rmSync, readdirSync, statSync, existsSync } from "node:fs";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
|
|
15
|
+
const MAX_AGE_DAYS = 3;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Auto-cleanup old review history files older than MAX_AGE_DAYS
|
|
19
|
+
*/
|
|
20
|
+
export function cleanupOldReviews(projectPath: string): number {
|
|
21
|
+
// Resolve path to prevent traversal attacks
|
|
22
|
+
const basePath = resolve(projectPath);
|
|
23
|
+
const reviewDir = join(basePath, ".write-review");
|
|
24
|
+
|
|
25
|
+
// Ensure reviewDir is within basePath (traversal guard)
|
|
26
|
+
if (!reviewDir.startsWith(basePath + '/')) {
|
|
27
|
+
return 0; // Invalid path, skip
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!existsSync(reviewDir)) {
|
|
31
|
+
return 0; // Directory doesn't exist
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let deleted = 0;
|
|
35
|
+
const files = readdirSync(reviewDir);
|
|
36
|
+
const cutoff = Date.now() - MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file === "status.json") continue; // Keep status file
|
|
40
|
+
// Sanitize filename to prevent path traversal
|
|
41
|
+
const sanitized = file.replace(/[^a-zA-Z0-9_.-]/g, '_');
|
|
42
|
+
if (sanitized !== file) continue; // Skip suspicious filenames
|
|
43
|
+
|
|
44
|
+
const filePath = join(reviewDir, sanitized);
|
|
45
|
+
try {
|
|
46
|
+
const stats = statSync(filePath);
|
|
47
|
+
if (stats.mtimeMs < cutoff) {
|
|
48
|
+
rmSync(filePath, { force: true });
|
|
49
|
+
deleted++;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore files that can't be read
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return deleted;
|
|
57
|
+
}
|
|
58
|
+
|
|
12
59
|
// Re-export all public APIs
|
|
13
60
|
export * from "./types.js";
|
|
14
61
|
export * from "./blackboard.js";
|
|
@@ -4,6 +4,26 @@
|
|
|
4
4
|
* Adds write-review hints to the agent's system prompt.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Find the project root (directory containing .git)
|
|
12
|
+
*/
|
|
13
|
+
function findProjectRoot(startPath: string): string {
|
|
14
|
+
let dir = startPath;
|
|
15
|
+
// Limit traversal to prevent infinite loops
|
|
16
|
+
for (let i = 0; i < 20; i++) {
|
|
17
|
+
if (existsSync(join(dir, ".git"))) {
|
|
18
|
+
return dir;
|
|
19
|
+
}
|
|
20
|
+
const parent = dirname(dir);
|
|
21
|
+
if (parent === dir) break; // Reached filesystem root
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
return startPath; // Fallback to start path
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
/**
|
|
8
28
|
* Write-review hint for system prompt
|
|
9
29
|
*/
|
|
@@ -105,7 +125,7 @@ export function injectWriterInstructions(
|
|
|
105
125
|
|
|
106
126
|
// System prompt injection
|
|
107
127
|
pi.on("before_agent_start", async (event) => {
|
|
108
|
-
const cwd = process.cwd?.() ?? "/";
|
|
128
|
+
const cwd = findProjectRoot(process.cwd?.() ?? "/");
|
|
109
129
|
if (cwd !== currentProject) {
|
|
110
130
|
currentProject = cwd;
|
|
111
131
|
blackboard = createBlackboard(cwd);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reviewer agent - evaluate code quality and request changes
|
|
3
|
+
argument-hint: "<task context>"
|
|
4
|
+
---
|
|
5
|
+
# Reviewer Agent
|
|
6
|
+
|
|
7
|
+
You are the **REVIEWER** agent. Your job is to evaluate code quality and request fixes.
|
|
8
|
+
|
|
9
|
+
## Review Criteria
|
|
10
|
+
|
|
11
|
+
1. **Correctness** - Does it solve the problem?
|
|
12
|
+
2. **Type Safety** - TypeScript types correct?
|
|
13
|
+
3. **Edge Cases** - Handles null/undefined/empty?
|
|
14
|
+
4. **Security** - No injection vulnerabilities?
|
|
15
|
+
5. **Performance** - No obvious O(n^2) loops?
|
|
16
|
+
6. **Testability** - Can be unit tested?
|
|
17
|
+
|
|
18
|
+
## Your Workflow
|
|
19
|
+
|
|
20
|
+
1. Read the code artifacts from blackboard
|
|
21
|
+
2. Evaluate against review criteria
|
|
22
|
+
3. Write your findings to the blackboard
|
|
23
|
+
4. Issue verdict:
|
|
24
|
+
- `APPROVED` - Code is ready
|
|
25
|
+
- `CHANGES_REQUESTED` - Issues found
|
|
26
|
+
- `BLOCKED` - Critical issues
|
|
27
|
+
|
|
28
|
+
## Blackboard Location
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
{project}/.write-review/status.json
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Verdict Format
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
## REVIEW FINDINGS
|
|
38
|
+
|
|
39
|
+
### Issues Found
|
|
40
|
+
- [issue description]
|
|
41
|
+
|
|
42
|
+
## VERDICT: CHANGES_REQUESTED
|
|
43
|
+
|
|
44
|
+
Please fix the issues above.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
## REVIEW FINDINGS
|
|
51
|
+
|
|
52
|
+
No critical issues found.
|
|
53
|
+
|
|
54
|
+
## VERDICT: APPROVED
|
|
55
|
+
|
|
56
|
+
Code is ready for build/commit.
|
|
57
|
+
```
|