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.
Files changed (150) hide show
  1. package/.pi/plugins/herdr-clipboard/herdr-plugin.toml +22 -0
  2. package/.pi/plugins/herdr-clipboard/start-bridge.sh +29 -0
  3. package/.pi/plugins/herdr-clipboard/tmux-watch.sh +39 -0
  4. package/CHANGELOG.md +41 -0
  5. package/README.md +87 -1
  6. package/config.toml.example +35 -0
  7. package/package.json +6 -4
  8. package/packages/architecture-generator/dist/adr.d.ts +15 -0
  9. package/packages/architecture-generator/dist/adr.d.ts.map +1 -0
  10. package/packages/architecture-generator/dist/adr.js +42 -0
  11. package/packages/architecture-generator/dist/adr.js.map +1 -0
  12. package/packages/architecture-generator/dist/index.d.ts +9 -0
  13. package/packages/architecture-generator/dist/index.d.ts.map +1 -0
  14. package/packages/architecture-generator/dist/index.js +9 -0
  15. package/packages/architecture-generator/dist/index.js.map +1 -0
  16. package/packages/architecture-generator/dist/mermaid.d.ts +30 -0
  17. package/packages/architecture-generator/dist/mermaid.d.ts.map +1 -0
  18. package/packages/architecture-generator/dist/mermaid.js +58 -0
  19. package/packages/architecture-generator/dist/mermaid.js.map +1 -0
  20. package/packages/architecture-generator/dist/types.d.ts +30 -0
  21. package/packages/architecture-generator/dist/types.d.ts.map +1 -0
  22. package/packages/architecture-generator/dist/types.js +7 -0
  23. package/packages/architecture-generator/dist/types.js.map +1 -0
  24. package/packages/architecture-generator/src/adr.ts +51 -0
  25. package/packages/architecture-generator/src/index.ts +9 -0
  26. package/packages/architecture-generator/src/mermaid.ts +76 -0
  27. package/packages/architecture-generator/src/types.ts +30 -0
  28. package/packages/capability-registry/src/types.js +10 -0
  29. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
  30. package/packages/clipboard/dist/src/copy-sync.js +45 -3
  31. package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
  32. package/packages/clipboard/dist/src/index.d.ts +1 -0
  33. package/packages/clipboard/dist/src/index.d.ts.map +1 -1
  34. package/packages/clipboard/dist/src/index.js +1 -0
  35. package/packages/clipboard/dist/src/index.js.map +1 -1
  36. package/packages/clipboard/dist/src/paste-bridge.d.ts +19 -0
  37. package/packages/clipboard/dist/src/paste-bridge.d.ts.map +1 -0
  38. package/packages/clipboard/dist/src/paste-bridge.js +78 -0
  39. package/packages/clipboard/dist/src/paste-bridge.js.map +1 -0
  40. package/packages/clipboard/src/copy-sync.ts +52 -3
  41. package/packages/clipboard/src/index.ts +1 -0
  42. package/packages/clipboard/src/paste-bridge.ts +86 -0
  43. package/packages/clipboard-plugin/dist/index.d.ts +28 -0
  44. package/packages/clipboard-plugin/dist/index.d.ts.map +1 -0
  45. package/packages/clipboard-plugin/dist/index.js +64 -0
  46. package/packages/clipboard-plugin/dist/index.js.map +1 -0
  47. package/packages/clipboard-plugin/dist/types.d.ts +30 -0
  48. package/packages/clipboard-plugin/dist/types.d.ts.map +1 -0
  49. package/packages/clipboard-plugin/dist/types.js +5 -0
  50. package/packages/clipboard-plugin/dist/types.js.map +1 -0
  51. package/packages/clipboard-plugin/src/index.ts +74 -0
  52. package/packages/clipboard-plugin/src/types.ts +30 -0
  53. package/packages/file-copy-helper/dist/src/extension.js +84 -25
  54. package/packages/file-copy-helper/src/extension.debug.ts +226 -0
  55. package/packages/file-copy-helper/src/extension.ts +89 -59
  56. package/packages/knowledge-graph/dist/graph-extractor.d.ts +27 -0
  57. package/packages/knowledge-graph/dist/graph-extractor.d.ts.map +1 -0
  58. package/packages/knowledge-graph/dist/graph-extractor.js +276 -0
  59. package/packages/knowledge-graph/dist/graph-extractor.js.map +1 -0
  60. package/packages/knowledge-graph/dist/index.d.ts +27 -0
  61. package/packages/knowledge-graph/dist/index.d.ts.map +1 -0
  62. package/packages/knowledge-graph/dist/index.js +27 -0
  63. package/packages/knowledge-graph/dist/index.js.map +1 -0
  64. package/packages/knowledge-graph/dist/mcp-client.d.ts +38 -0
  65. package/packages/knowledge-graph/dist/mcp-client.d.ts.map +1 -0
  66. package/packages/knowledge-graph/dist/mcp-client.js +157 -0
  67. package/packages/knowledge-graph/dist/mcp-client.js.map +1 -0
  68. package/packages/knowledge-graph/dist/provenance-linker.d.ts +19 -0
  69. package/packages/knowledge-graph/dist/provenance-linker.d.ts.map +1 -0
  70. package/packages/knowledge-graph/dist/provenance-linker.js +91 -0
  71. package/packages/knowledge-graph/dist/provenance-linker.js.map +1 -0
  72. package/packages/knowledge-graph/dist/types.d.ts +53 -0
  73. package/packages/knowledge-graph/dist/types.d.ts.map +1 -0
  74. package/packages/knowledge-graph/dist/types.js +7 -0
  75. package/packages/knowledge-graph/dist/types.js.map +1 -0
  76. package/packages/knowledge-graph/src/graph-extractor.ts +338 -0
  77. package/packages/knowledge-graph/src/index.ts +27 -0
  78. package/packages/knowledge-graph/src/mcp-client.ts +237 -0
  79. package/packages/knowledge-graph/src/provenance-linker.ts +101 -0
  80. package/packages/knowledge-graph/src/types.ts +86 -0
  81. package/packages/milestone-manager/dist/engine.d.ts +27 -0
  82. package/packages/milestone-manager/dist/engine.d.ts.map +1 -0
  83. package/packages/milestone-manager/dist/engine.js +78 -0
  84. package/packages/milestone-manager/dist/engine.js.map +1 -0
  85. package/packages/milestone-manager/dist/index.d.ts +6 -0
  86. package/packages/milestone-manager/dist/index.d.ts.map +1 -0
  87. package/packages/milestone-manager/dist/index.js +6 -0
  88. package/packages/milestone-manager/dist/index.js.map +1 -0
  89. package/packages/milestone-manager/dist/types.d.ts +25 -0
  90. package/packages/milestone-manager/dist/types.d.ts.map +1 -0
  91. package/packages/milestone-manager/dist/types.js +7 -0
  92. package/packages/milestone-manager/dist/types.js.map +1 -0
  93. package/packages/milestone-manager/src/engine.ts +105 -0
  94. package/packages/milestone-manager/src/index.ts +6 -0
  95. package/packages/milestone-manager/src/types.ts +28 -0
  96. package/packages/notification/dist/adapters/line-adapter.d.ts +17 -0
  97. package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -0
  98. package/packages/notification/dist/adapters/line-adapter.js +99 -0
  99. package/packages/notification/dist/adapters/line-adapter.js.map +1 -0
  100. package/packages/notification/dist/index.d.ts +1 -0
  101. package/packages/notification/dist/index.d.ts.map +1 -1
  102. package/packages/notification/dist/index.js +1 -0
  103. package/packages/notification/dist/index.js.map +1 -1
  104. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  105. package/packages/notification/dist/notification-center.js +4 -0
  106. package/packages/notification/dist/notification-center.js.map +1 -1
  107. package/packages/notification/dist/types.d.ts +6 -2
  108. package/packages/notification/dist/types.d.ts.map +1 -1
  109. package/packages/notification/src/adapters/line-adapter.ts +119 -0
  110. package/packages/notification/src/index.ts +1 -0
  111. package/packages/notification/src/notification-center.ts +6 -0
  112. package/packages/notification/src/types.ts +7 -2
  113. package/packages/project-bootstrap/dist/bootstrap.d.ts +23 -0
  114. package/packages/project-bootstrap/dist/bootstrap.d.ts.map +1 -0
  115. package/packages/project-bootstrap/dist/bootstrap.js +128 -0
  116. package/packages/project-bootstrap/dist/bootstrap.js.map +1 -0
  117. package/packages/project-bootstrap/dist/frappe.d.ts +8 -0
  118. package/packages/project-bootstrap/dist/frappe.d.ts.map +1 -0
  119. package/packages/project-bootstrap/dist/frappe.js +82 -0
  120. package/packages/project-bootstrap/dist/frappe.js.map +1 -0
  121. package/packages/project-bootstrap/dist/index.d.ts +27 -0
  122. package/packages/project-bootstrap/dist/index.d.ts.map +1 -0
  123. package/packages/project-bootstrap/dist/index.js +27 -0
  124. package/packages/project-bootstrap/dist/index.js.map +1 -0
  125. package/packages/project-bootstrap/dist/minimal.d.ts +8 -0
  126. package/packages/project-bootstrap/dist/minimal.d.ts.map +1 -0
  127. package/packages/project-bootstrap/dist/minimal.js +35 -0
  128. package/packages/project-bootstrap/dist/minimal.js.map +1 -0
  129. package/packages/project-bootstrap/dist/types.d.ts +61 -0
  130. package/packages/project-bootstrap/dist/types.d.ts.map +1 -0
  131. package/packages/project-bootstrap/dist/types.js +7 -0
  132. package/packages/project-bootstrap/dist/types.js.map +1 -0
  133. package/packages/project-bootstrap/dist/typescript.d.ts +8 -0
  134. package/packages/project-bootstrap/dist/typescript.d.ts.map +1 -0
  135. package/packages/project-bootstrap/dist/typescript.js +103 -0
  136. package/packages/project-bootstrap/dist/typescript.js.map +1 -0
  137. package/packages/project-bootstrap/src/bootstrap.ts +166 -0
  138. package/packages/project-bootstrap/src/frappe.ts +97 -0
  139. package/packages/project-bootstrap/src/index.ts +27 -0
  140. package/packages/project-bootstrap/src/minimal.ts +45 -0
  141. package/packages/project-bootstrap/src/types.ts +66 -0
  142. package/packages/project-bootstrap/src/typescript.ts +114 -0
  143. package/packages/provider-router/dist/src/types.js +1 -1
  144. package/packages/write-review/src/index.ts +47 -0
  145. package/packages/write-review/src/injection.ts +21 -1
  146. package/prompts/agents/reviewer.md +57 -0
  147. package/prompts/agents/writer.md +42 -0
  148. package/prompts/slash-commands.md +40 -0
  149. package/prompts/write-review.md +50 -0
  150. 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
+ };
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Provider Router Types (RFC-0054)
3
3
  */
4
- // ─── Latency Ranking ─────────────────────────────────────────────────
4
+ // --- Latency Ranking -------------------------------------------------
5
5
  export const LATENCY_RANK = {
6
6
  fast: 1,
7
7
  medium: 2,
@@ -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
+ ```