vralphy 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +512 -0
- package/bin/vralphy.js +3 -0
- package/dist/commands/build.d.ts +9 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +176 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/cleanup.d.ts +19 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +159 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/cleanup.test.d.ts +2 -0
- package/dist/commands/cleanup.test.d.ts.map +1 -0
- package/dist/commands/cleanup.test.js +389 -0
- package/dist/commands/cleanup.test.js.map +1 -0
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +120 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/plan.d.ts +9 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +147 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/spec.d.ts +9 -0
- package/dist/commands/spec.d.ts.map +1 -0
- package/dist/commands/spec.js +111 -0
- package/dist/commands/spec.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/agents.d.ts +32 -0
- package/dist/lib/agents.d.ts.map +1 -0
- package/dist/lib/agents.js +96 -0
- package/dist/lib/agents.js.map +1 -0
- package/dist/lib/config.d.ts +54 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +199 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/config.test.d.ts +2 -0
- package/dist/lib/config.test.d.ts.map +1 -0
- package/dist/lib/config.test.js +57 -0
- package/dist/lib/config.test.js.map +1 -0
- package/dist/lib/context.d.ts +29 -0
- package/dist/lib/context.d.ts.map +1 -0
- package/dist/lib/context.js +175 -0
- package/dist/lib/context.js.map +1 -0
- package/dist/lib/engines/base.d.ts +75 -0
- package/dist/lib/engines/base.d.ts.map +1 -0
- package/dist/lib/engines/base.js +62 -0
- package/dist/lib/engines/base.js.map +1 -0
- package/dist/lib/engines/base.test.d.ts +2 -0
- package/dist/lib/engines/base.test.d.ts.map +1 -0
- package/dist/lib/engines/base.test.js +28 -0
- package/dist/lib/engines/base.test.js.map +1 -0
- package/dist/lib/engines/claude.d.ts +12 -0
- package/dist/lib/engines/claude.d.ts.map +1 -0
- package/dist/lib/engines/claude.js +156 -0
- package/dist/lib/engines/claude.js.map +1 -0
- package/dist/lib/engines/codex.d.ts +28 -0
- package/dist/lib/engines/codex.d.ts.map +1 -0
- package/dist/lib/engines/codex.js +177 -0
- package/dist/lib/engines/codex.js.map +1 -0
- package/dist/lib/engines/index.d.ts +19 -0
- package/dist/lib/engines/index.d.ts.map +1 -0
- package/dist/lib/engines/index.js +40 -0
- package/dist/lib/engines/index.js.map +1 -0
- package/dist/lib/engines/opencode.d.ts +14 -0
- package/dist/lib/engines/opencode.d.ts.map +1 -0
- package/dist/lib/engines/opencode.js +127 -0
- package/dist/lib/engines/opencode.js.map +1 -0
- package/dist/lib/events/index.d.ts +6 -0
- package/dist/lib/events/index.d.ts.map +1 -0
- package/dist/lib/events/index.js +5 -0
- package/dist/lib/events/index.js.map +1 -0
- package/dist/lib/events/types.d.ts +93 -0
- package/dist/lib/events/types.d.ts.map +1 -0
- package/dist/lib/events/types.js +7 -0
- package/dist/lib/events/types.js.map +1 -0
- package/dist/lib/events/writer.d.ts +68 -0
- package/dist/lib/events/writer.d.ts.map +1 -0
- package/dist/lib/events/writer.js +178 -0
- package/dist/lib/events/writer.js.map +1 -0
- package/dist/lib/events.d.ts +33 -0
- package/dist/lib/events.d.ts.map +1 -0
- package/dist/lib/events.js +81 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/events.test.d.ts +2 -0
- package/dist/lib/events.test.d.ts.map +1 -0
- package/dist/lib/events.test.js +123 -0
- package/dist/lib/events.test.js.map +1 -0
- package/dist/lib/file-injection.d.ts +32 -0
- package/dist/lib/file-injection.d.ts.map +1 -0
- package/dist/lib/file-injection.js +138 -0
- package/dist/lib/file-injection.js.map +1 -0
- package/dist/lib/file-injection.test.d.ts +2 -0
- package/dist/lib/file-injection.test.d.ts.map +1 -0
- package/dist/lib/file-injection.test.js +508 -0
- package/dist/lib/file-injection.test.js.map +1 -0
- package/dist/lib/init.d.ts +27 -0
- package/dist/lib/init.d.ts.map +1 -0
- package/dist/lib/init.js +363 -0
- package/dist/lib/init.js.map +1 -0
- package/dist/lib/init.test.d.ts +2 -0
- package/dist/lib/init.test.d.ts.map +1 -0
- package/dist/lib/init.test.js +315 -0
- package/dist/lib/init.test.js.map +1 -0
- package/dist/lib/plan.d.ts +11 -0
- package/dist/lib/plan.d.ts.map +1 -0
- package/dist/lib/plan.js +22 -0
- package/dist/lib/plan.js.map +1 -0
- package/dist/lib/plan.test.d.ts +2 -0
- package/dist/lib/plan.test.d.ts.map +1 -0
- package/dist/lib/plan.test.js +70 -0
- package/dist/lib/plan.test.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +18 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -0
- package/dist/lib/prompts/codex.js +82 -0
- package/dist/lib/prompts/codex.js.map +1 -0
- package/dist/lib/prompts/opencode.d.ts +16 -0
- package/dist/lib/prompts/opencode.d.ts.map +1 -0
- package/dist/lib/prompts/opencode.js +71 -0
- package/dist/lib/prompts/opencode.js.map +1 -0
- package/dist/lib/prompts.d.ts +57 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +255 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/prompts.test.d.ts +2 -0
- package/dist/lib/prompts.test.d.ts.map +1 -0
- package/dist/lib/prompts.test.js +128 -0
- package/dist/lib/prompts.test.js.map +1 -0
- package/dist/lib/skills.d.ts +36 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +132 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/slack.d.ts +43 -0
- package/dist/lib/slack.d.ts.map +1 -0
- package/dist/lib/slack.js +130 -0
- package/dist/lib/slack.js.map +1 -0
- package/dist/lib/slack.test.d.ts +2 -0
- package/dist/lib/slack.test.d.ts.map +1 -0
- package/dist/lib/slack.test.js +74 -0
- package/dist/lib/slack.test.js.map +1 -0
- package/dist/lib/templates/injections.d.ts +18 -0
- package/dist/lib/templates/injections.d.ts.map +1 -0
- package/dist/lib/templates/injections.js +32 -0
- package/dist/lib/templates/injections.js.map +1 -0
- package/dist/lib/templates/readme.d.ts +6 -0
- package/dist/lib/templates/readme.d.ts.map +1 -0
- package/dist/lib/templates/readme.js +168 -0
- package/dist/lib/templates/readme.js.map +1 -0
- package/docs/COMMANDS.md +664 -0
- package/docs/DESIGN.md +537 -0
- package/docs/EXAMPLES.md +812 -0
- package/docs/METHODOLOGY.md +390 -0
- package/docs/README.md +110 -0
- package/docs/WORKFLOWS.md +808 -0
- package/llms.txt +104 -0
- package/package.json +58 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-injection.d.ts","sourceRoot":"","sources":["../../src/lib/file-injection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IAClE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAE9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAIxF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAsD1B;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAkE1B"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for marker-based content injection and removal in files
|
|
3
|
+
*/
|
|
4
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
/**
|
|
7
|
+
* Check if content already has injection markers
|
|
8
|
+
*/
|
|
9
|
+
export function hasMarkers(content, markers) {
|
|
10
|
+
return content.includes(markers.start) && content.includes(markers.end);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Check if content has incomplete markers (only start or end)
|
|
14
|
+
*/
|
|
15
|
+
export function hasIncompleteMarkers(content, markers) {
|
|
16
|
+
const hasStart = content.includes(markers.start);
|
|
17
|
+
const hasEnd = content.includes(markers.end);
|
|
18
|
+
return (hasStart && !hasEnd) || (!hasStart && hasEnd);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Inject content into a file between markers
|
|
22
|
+
* If markers exist, replaces content between them
|
|
23
|
+
* If markers don't exist, appends to end of file
|
|
24
|
+
*/
|
|
25
|
+
export async function injectContent(filePath, content, markers) {
|
|
26
|
+
try {
|
|
27
|
+
// Check if file exists
|
|
28
|
+
if (!existsSync(filePath)) {
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
action: 'skipped',
|
|
32
|
+
message: 'File does not exist',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const fileContent = await readFile(filePath, 'utf-8');
|
|
36
|
+
// Check for incomplete markers
|
|
37
|
+
if (hasIncompleteMarkers(fileContent, markers)) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
action: 'skipped',
|
|
41
|
+
message: 'File has incomplete markers (only start or end)',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Build the injection block
|
|
45
|
+
const injectionBlock = `${markers.start}${content}${markers.end}`;
|
|
46
|
+
let newContent;
|
|
47
|
+
let action;
|
|
48
|
+
if (hasMarkers(fileContent, markers)) {
|
|
49
|
+
// Replace existing content between markers
|
|
50
|
+
const startIdx = fileContent.indexOf(markers.start);
|
|
51
|
+
const endIdx = fileContent.indexOf(markers.end) + markers.end.length;
|
|
52
|
+
newContent = fileContent.slice(0, startIdx) + injectionBlock + fileContent.slice(endIdx);
|
|
53
|
+
action = 'replaced';
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Append to end of file
|
|
57
|
+
const separator = fileContent.endsWith('\n') ? '\n' : '\n\n';
|
|
58
|
+
newContent = fileContent + separator + injectionBlock + '\n';
|
|
59
|
+
action = 'injected';
|
|
60
|
+
}
|
|
61
|
+
await writeFile(filePath, newContent);
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
action,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return {
|
|
69
|
+
success: false,
|
|
70
|
+
action: 'error',
|
|
71
|
+
message: error instanceof Error ? error.message : String(error),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Remove injected content from a file
|
|
77
|
+
* Removes everything between and including the markers
|
|
78
|
+
*/
|
|
79
|
+
export async function removeInjectedContent(filePath, markers) {
|
|
80
|
+
try {
|
|
81
|
+
// Check if file exists
|
|
82
|
+
if (!existsSync(filePath)) {
|
|
83
|
+
return {
|
|
84
|
+
success: false,
|
|
85
|
+
action: 'skipped',
|
|
86
|
+
message: 'File does not exist',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const fileContent = await readFile(filePath, 'utf-8');
|
|
90
|
+
// Check for incomplete markers
|
|
91
|
+
if (hasIncompleteMarkers(fileContent, markers)) {
|
|
92
|
+
return {
|
|
93
|
+
success: false,
|
|
94
|
+
action: 'skipped',
|
|
95
|
+
message: 'File has incomplete markers (only start or end)',
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// Check if markers exist
|
|
99
|
+
if (!hasMarkers(fileContent, markers)) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
action: 'skipped',
|
|
103
|
+
message: 'No injection markers found',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Remove content between markers (including markers)
|
|
107
|
+
const startIdx = fileContent.indexOf(markers.start);
|
|
108
|
+
const endIdx = fileContent.indexOf(markers.end) + markers.end.length;
|
|
109
|
+
// Include any trailing newline in removal
|
|
110
|
+
let removeEndIdx = endIdx;
|
|
111
|
+
if (fileContent[endIdx] === '\n') {
|
|
112
|
+
removeEndIdx = endIdx + 1;
|
|
113
|
+
}
|
|
114
|
+
// Include any leading newlines if preceded by content
|
|
115
|
+
let removeStartIdx = startIdx;
|
|
116
|
+
if (startIdx > 0 && fileContent[startIdx - 1] === '\n') {
|
|
117
|
+
removeStartIdx = startIdx - 1;
|
|
118
|
+
// Remove extra blank line if there's another newline before
|
|
119
|
+
if (removeStartIdx > 0 && fileContent[removeStartIdx - 1] === '\n') {
|
|
120
|
+
removeStartIdx = removeStartIdx - 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const newContent = fileContent.slice(0, removeStartIdx) + fileContent.slice(removeEndIdx);
|
|
124
|
+
await writeFile(filePath, newContent);
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
action: 'removed',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
action: 'error',
|
|
134
|
+
message: error instanceof Error ? error.message : String(error),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=file-injection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-injection.js","sourceRoot":"","sources":["../../src/lib/file-injection.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAahC;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,OAAyB;IACnE,OAAO,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC1E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAe,EAAE,OAAyB;IAC7E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,OAAO,CAAC,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,OAAe,EACf,OAAyB;IAEzB,IAAI,CAAC;QACH,uBAAuB;QACvB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,qBAAqB;aAC/B,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEtD,+BAA+B;QAC/B,IAAI,oBAAoB,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;YAC/C,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,iDAAiD;aAC3D,CAAC;QACJ,CAAC;QAED,4BAA4B;QAC5B,MAAM,cAAc,GAAG,GAAG,OAAO,CAAC,KAAK,GAAG,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAElE,IAAI,UAAkB,CAAC;QACvB,IAAI,MAA+B,CAAC;QAEpC,IAAI,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;YACrC,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;YACrE,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,GAAG,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YACzF,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,wBAAwB;YACxB,MAAM,SAAS,GAAG,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;YAC7D,UAAU,GAAG,WAAW,GAAG,SAAS,GAAG,cAAc,GAAG,IAAI,CAAC;YAC7D,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;QAED,MAAM,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEtC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM;SACP,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAChE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAgB,EAChB,OAAyB;IAEzB,IAAI,CAAC;QACH,uBAAuB;QACvB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,qBAAqB;aAC/B,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEtD,+BAA+B;QAC/B,IAAI,oBAAoB,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;YAC/C,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,iDAAiD;aAC3D,CAAC;QACJ,CAAC;QAED,yBAAyB;QACzB,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;YACtC,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,SAAS;gBACjB,OAAO,EAAE,4BAA4B;aACtC,CAAC;QACJ,CAAC;QAED,qDAAqD;QACrD,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC;QAErE,0CAA0C;QAC1C,IAAI,YAAY,GAAG,MAAM,CAAC;QAC1B,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;YACjC,YAAY,GAAG,MAAM,GAAG,CAAC,CAAC;QAC5B,CAAC;QAED,sDAAsD;QACtD,IAAI,cAAc,GAAG,QAAQ,CAAC;QAC9B,IAAI,QAAQ,GAAG,CAAC,IAAI,WAAW,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACvD,cAAc,GAAG,QAAQ,GAAG,CAAC,CAAC;YAC9B,4DAA4D;YAC5D,IAAI,cAAc,GAAG,CAAC,IAAI,WAAW,CAAC,cAAc,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACnE,cAAc,GAAG,cAAc,GAAG,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;QAED,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAE1F,MAAM,SAAS,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QAEtC,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAChE,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-injection.test.d.ts","sourceRoot":"","sources":["../../src/lib/file-injection.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdir, writeFile, readFile, rm } from 'fs/promises';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { injectContent, removeInjectedContent, hasMarkers, hasIncompleteMarkers, } from './file-injection.js';
|
|
6
|
+
import { INJECTION_MARKERS, CLAUDE_MD_INJECTION, AGENTS_MD_INJECTION } from './templates/injections.js';
|
|
7
|
+
const TEST_DIR = '/tmp/vralphy-injection-test';
|
|
8
|
+
describe('file-injection', () => {
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
if (existsSync(TEST_DIR)) {
|
|
11
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
await mkdir(TEST_DIR, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
if (existsSync(TEST_DIR)) {
|
|
17
|
+
await rm(TEST_DIR, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
describe('hasMarkers', () => {
|
|
21
|
+
it('should return true when both markers present', () => {
|
|
22
|
+
const content = `# Title\n${INJECTION_MARKERS.start}\nInjected\n${INJECTION_MARKERS.end}\n`;
|
|
23
|
+
expect(hasMarkers(content, INJECTION_MARKERS)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should return false when no markers present', () => {
|
|
26
|
+
const content = '# Title\nSome content\n';
|
|
27
|
+
expect(hasMarkers(content, INJECTION_MARKERS)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
it('should return false when only start marker present', () => {
|
|
30
|
+
const content = `# Title\n${INJECTION_MARKERS.start}\nContent\n`;
|
|
31
|
+
expect(hasMarkers(content, INJECTION_MARKERS)).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
it('should return false when only end marker present', () => {
|
|
34
|
+
const content = `# Title\n${INJECTION_MARKERS.end}\nContent\n`;
|
|
35
|
+
expect(hasMarkers(content, INJECTION_MARKERS)).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
it('should work with markers in middle of file', () => {
|
|
38
|
+
const content = `# Header\n\nParagraph.\n\n${INJECTION_MARKERS.start}\nMiddle\n${INJECTION_MARKERS.end}\n\n## Footer\n`;
|
|
39
|
+
expect(hasMarkers(content, INJECTION_MARKERS)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
it('should work with custom markers', () => {
|
|
42
|
+
const customMarkers = { start: '<!-- BEGIN -->', end: '<!-- END -->' };
|
|
43
|
+
const content = '# Title\n<!-- BEGIN -->\nContent\n<!-- END -->\n';
|
|
44
|
+
expect(hasMarkers(content, customMarkers)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('hasIncompleteMarkers', () => {
|
|
48
|
+
it('should return true when only start marker present', () => {
|
|
49
|
+
const content = `# Title\n${INJECTION_MARKERS.start}\nContent\n`;
|
|
50
|
+
expect(hasIncompleteMarkers(content, INJECTION_MARKERS)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
it('should return true when only end marker present', () => {
|
|
53
|
+
const content = `# Title\n${INJECTION_MARKERS.end}\nContent\n`;
|
|
54
|
+
expect(hasIncompleteMarkers(content, INJECTION_MARKERS)).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
it('should return false when both markers present', () => {
|
|
57
|
+
const content = `# Title\n${INJECTION_MARKERS.start}\nInjected\n${INJECTION_MARKERS.end}\n`;
|
|
58
|
+
expect(hasIncompleteMarkers(content, INJECTION_MARKERS)).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
it('should return false when no markers present', () => {
|
|
61
|
+
const content = '# Title\nSome content\n';
|
|
62
|
+
expect(hasIncompleteMarkers(content, INJECTION_MARKERS)).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('injectContent', () => {
|
|
66
|
+
it('should inject content at end of file when no markers exist', async () => {
|
|
67
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
68
|
+
await writeFile(filePath, '# Title\n\nExisting content.\n');
|
|
69
|
+
const result = await injectContent(filePath, '\nNew section\n', INJECTION_MARKERS);
|
|
70
|
+
expect(result.success).toBe(true);
|
|
71
|
+
expect(result.action).toBe('injected');
|
|
72
|
+
const content = await readFile(filePath, 'utf-8');
|
|
73
|
+
expect(content).toContain(INJECTION_MARKERS.start);
|
|
74
|
+
expect(content).toContain('New section');
|
|
75
|
+
expect(content).toContain(INJECTION_MARKERS.end);
|
|
76
|
+
expect(content).toContain('Existing content.');
|
|
77
|
+
});
|
|
78
|
+
it('should replace content when markers already exist', async () => {
|
|
79
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
80
|
+
await writeFile(filePath, `# Title\n\n${INJECTION_MARKERS.start}\nOld content\n${INJECTION_MARKERS.end}\n\nMore content.\n`);
|
|
81
|
+
const result = await injectContent(filePath, '\nNew content\n', INJECTION_MARKERS);
|
|
82
|
+
expect(result.success).toBe(true);
|
|
83
|
+
expect(result.action).toBe('replaced');
|
|
84
|
+
const content = await readFile(filePath, 'utf-8');
|
|
85
|
+
expect(content).toContain('New content');
|
|
86
|
+
expect(content).not.toContain('Old content');
|
|
87
|
+
expect(content).toContain('More content.');
|
|
88
|
+
});
|
|
89
|
+
it('should skip when file does not exist', async () => {
|
|
90
|
+
const filePath = join(TEST_DIR, 'nonexistent.md');
|
|
91
|
+
const result = await injectContent(filePath, '\nContent\n', INJECTION_MARKERS);
|
|
92
|
+
expect(result.success).toBe(false);
|
|
93
|
+
expect(result.action).toBe('skipped');
|
|
94
|
+
expect(result.message).toBe('File does not exist');
|
|
95
|
+
});
|
|
96
|
+
it('should skip when file has incomplete markers', async () => {
|
|
97
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
98
|
+
await writeFile(filePath, `# Title\n\n${INJECTION_MARKERS.start}\nBroken content\n`);
|
|
99
|
+
const result = await injectContent(filePath, '\nContent\n', INJECTION_MARKERS);
|
|
100
|
+
expect(result.success).toBe(false);
|
|
101
|
+
expect(result.action).toBe('skipped');
|
|
102
|
+
expect(result.message).toContain('incomplete markers');
|
|
103
|
+
});
|
|
104
|
+
it('should preserve content before and after injection point', async () => {
|
|
105
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
106
|
+
const originalContent = `# Header
|
|
107
|
+
|
|
108
|
+
First paragraph with important info.
|
|
109
|
+
|
|
110
|
+
## Section A
|
|
111
|
+
|
|
112
|
+
Content A.
|
|
113
|
+
|
|
114
|
+
## Section B
|
|
115
|
+
|
|
116
|
+
Content B.
|
|
117
|
+
`;
|
|
118
|
+
await writeFile(filePath, originalContent);
|
|
119
|
+
await injectContent(filePath, '\n## Injected\n\nNew stuff.\n', INJECTION_MARKERS);
|
|
120
|
+
const content = await readFile(filePath, 'utf-8');
|
|
121
|
+
expect(content).toContain('# Header');
|
|
122
|
+
expect(content).toContain('First paragraph with important info.');
|
|
123
|
+
expect(content).toContain('## Section A');
|
|
124
|
+
expect(content).toContain('Content A.');
|
|
125
|
+
expect(content).toContain('## Section B');
|
|
126
|
+
expect(content).toContain('Content B.');
|
|
127
|
+
expect(content).toContain('## Injected');
|
|
128
|
+
expect(content).toContain('New stuff.');
|
|
129
|
+
});
|
|
130
|
+
it('should handle empty file', async () => {
|
|
131
|
+
const filePath = join(TEST_DIR, 'empty.md');
|
|
132
|
+
await writeFile(filePath, '');
|
|
133
|
+
const result = await injectContent(filePath, '\nContent\n', INJECTION_MARKERS);
|
|
134
|
+
expect(result.success).toBe(true);
|
|
135
|
+
const content = await readFile(filePath, 'utf-8');
|
|
136
|
+
expect(content).toContain(INJECTION_MARKERS.start);
|
|
137
|
+
expect(content).toContain('Content');
|
|
138
|
+
expect(content).toContain(INJECTION_MARKERS.end);
|
|
139
|
+
});
|
|
140
|
+
it('should handle file without trailing newline', async () => {
|
|
141
|
+
const filePath = join(TEST_DIR, 'no-newline.md');
|
|
142
|
+
await writeFile(filePath, '# Title\n\nNo trailing newline');
|
|
143
|
+
const result = await injectContent(filePath, '\nContent\n', INJECTION_MARKERS);
|
|
144
|
+
expect(result.success).toBe(true);
|
|
145
|
+
const content = await readFile(filePath, 'utf-8');
|
|
146
|
+
expect(content).toContain('No trailing newline');
|
|
147
|
+
expect(content).toContain(INJECTION_MARKERS.start);
|
|
148
|
+
});
|
|
149
|
+
it('should work with actual CLAUDE_MD_INJECTION content', async () => {
|
|
150
|
+
const filePath = join(TEST_DIR, 'CLAUDE.md');
|
|
151
|
+
await writeFile(filePath, `# vralphy
|
|
152
|
+
|
|
153
|
+
CLI tool implementing the Ralph Playbook.
|
|
154
|
+
|
|
155
|
+
## Commands
|
|
156
|
+
|
|
157
|
+
\`\`\`bash
|
|
158
|
+
npm run build
|
|
159
|
+
npm test
|
|
160
|
+
\`\`\`
|
|
161
|
+
`);
|
|
162
|
+
const result = await injectContent(filePath, CLAUDE_MD_INJECTION, INJECTION_MARKERS);
|
|
163
|
+
expect(result.success).toBe(true);
|
|
164
|
+
expect(result.action).toBe('injected');
|
|
165
|
+
const content = await readFile(filePath, 'utf-8');
|
|
166
|
+
expect(content).toContain('## vralphy Integration');
|
|
167
|
+
expect(content).toContain('@.vralphy/README.md');
|
|
168
|
+
expect(content).toContain('@.vralphy/AGENTS.md');
|
|
169
|
+
expect(content).toContain('@IMPLEMENTATION_PLAN.md');
|
|
170
|
+
// Original content preserved
|
|
171
|
+
expect(content).toContain('# vralphy');
|
|
172
|
+
expect(content).toContain('npm run build');
|
|
173
|
+
});
|
|
174
|
+
it('should work with actual AGENTS_MD_INJECTION content', async () => {
|
|
175
|
+
const filePath = join(TEST_DIR, 'AGENTS.md');
|
|
176
|
+
await writeFile(filePath, `# AGENTS.md
|
|
177
|
+
|
|
178
|
+
Operational guide for AI agents.
|
|
179
|
+
|
|
180
|
+
## Build & Validate
|
|
181
|
+
|
|
182
|
+
\`\`\`bash
|
|
183
|
+
npm install
|
|
184
|
+
\`\`\`
|
|
185
|
+
`);
|
|
186
|
+
const result = await injectContent(filePath, AGENTS_MD_INJECTION, INJECTION_MARKERS);
|
|
187
|
+
expect(result.success).toBe(true);
|
|
188
|
+
const content = await readFile(filePath, 'utf-8');
|
|
189
|
+
expect(content).toContain('## vralphy');
|
|
190
|
+
expect(content).toContain('.vralphy/README.md');
|
|
191
|
+
// Original content preserved
|
|
192
|
+
expect(content).toContain('# AGENTS.md');
|
|
193
|
+
expect(content).toContain('Operational guide');
|
|
194
|
+
});
|
|
195
|
+
it('should handle multiple inject/replace cycles', async () => {
|
|
196
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
197
|
+
await writeFile(filePath, '# Project\n');
|
|
198
|
+
// First injection
|
|
199
|
+
await injectContent(filePath, '\nVersion 1\n', INJECTION_MARKERS);
|
|
200
|
+
let content = await readFile(filePath, 'utf-8');
|
|
201
|
+
expect(content).toContain('Version 1');
|
|
202
|
+
// Second injection (replace)
|
|
203
|
+
await injectContent(filePath, '\nVersion 2\n', INJECTION_MARKERS);
|
|
204
|
+
content = await readFile(filePath, 'utf-8');
|
|
205
|
+
expect(content).toContain('Version 2');
|
|
206
|
+
expect(content).not.toContain('Version 1');
|
|
207
|
+
// Third injection (replace)
|
|
208
|
+
await injectContent(filePath, '\nVersion 3\n', INJECTION_MARKERS);
|
|
209
|
+
content = await readFile(filePath, 'utf-8');
|
|
210
|
+
expect(content).toContain('Version 3');
|
|
211
|
+
expect(content).not.toContain('Version 2');
|
|
212
|
+
// Markers should appear exactly once
|
|
213
|
+
const startCount = (content.match(new RegExp(INJECTION_MARKERS.start, 'g')) || []).length;
|
|
214
|
+
const endCount = (content.match(new RegExp(INJECTION_MARKERS.end, 'g')) || []).length;
|
|
215
|
+
expect(startCount).toBe(1);
|
|
216
|
+
expect(endCount).toBe(1);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('removeInjectedContent', () => {
|
|
220
|
+
it('should remove injected content and markers', async () => {
|
|
221
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
222
|
+
await writeFile(filePath, `# Title\n\n${INJECTION_MARKERS.start}\nInjected content\n${INJECTION_MARKERS.end}\n\nRemaining content.\n`);
|
|
223
|
+
const result = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
224
|
+
expect(result.success).toBe(true);
|
|
225
|
+
expect(result.action).toBe('removed');
|
|
226
|
+
const content = await readFile(filePath, 'utf-8');
|
|
227
|
+
expect(content).not.toContain(INJECTION_MARKERS.start);
|
|
228
|
+
expect(content).not.toContain('Injected content');
|
|
229
|
+
expect(content).not.toContain(INJECTION_MARKERS.end);
|
|
230
|
+
expect(content).toContain('# Title');
|
|
231
|
+
expect(content).toContain('Remaining content.');
|
|
232
|
+
});
|
|
233
|
+
it('should skip when file does not exist', async () => {
|
|
234
|
+
const filePath = join(TEST_DIR, 'nonexistent.md');
|
|
235
|
+
const result = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
236
|
+
expect(result.success).toBe(false);
|
|
237
|
+
expect(result.action).toBe('skipped');
|
|
238
|
+
expect(result.message).toBe('File does not exist');
|
|
239
|
+
});
|
|
240
|
+
it('should skip when no markers found', async () => {
|
|
241
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
242
|
+
await writeFile(filePath, '# Title\n\nNo markers here.\n');
|
|
243
|
+
const result = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
244
|
+
expect(result.success).toBe(false);
|
|
245
|
+
expect(result.action).toBe('skipped');
|
|
246
|
+
expect(result.message).toBe('No injection markers found');
|
|
247
|
+
});
|
|
248
|
+
it('should skip when file has incomplete markers', async () => {
|
|
249
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
250
|
+
await writeFile(filePath, `# Title\n\n${INJECTION_MARKERS.start}\nBroken\n`);
|
|
251
|
+
const result = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
252
|
+
expect(result.success).toBe(false);
|
|
253
|
+
expect(result.action).toBe('skipped');
|
|
254
|
+
expect(result.message).toContain('incomplete markers');
|
|
255
|
+
});
|
|
256
|
+
it('should leave file non-empty after removal', async () => {
|
|
257
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
258
|
+
await writeFile(filePath, `# Project\n\n${INJECTION_MARKERS.start}\nInjected\n${INJECTION_MARKERS.end}\n`);
|
|
259
|
+
await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
260
|
+
const content = await readFile(filePath, 'utf-8');
|
|
261
|
+
expect(content.length).toBeGreaterThan(0);
|
|
262
|
+
expect(content).toContain('# Project');
|
|
263
|
+
});
|
|
264
|
+
it('should preserve content before injection', async () => {
|
|
265
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
266
|
+
await writeFile(filePath, `# Header
|
|
267
|
+
|
|
268
|
+
First section.
|
|
269
|
+
|
|
270
|
+
## Section A
|
|
271
|
+
|
|
272
|
+
Content A.
|
|
273
|
+
|
|
274
|
+
${INJECTION_MARKERS.start}
|
|
275
|
+
## Injected Section
|
|
276
|
+
|
|
277
|
+
This will be removed.
|
|
278
|
+
${INJECTION_MARKERS.end}
|
|
279
|
+
`);
|
|
280
|
+
await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
281
|
+
const content = await readFile(filePath, 'utf-8');
|
|
282
|
+
expect(content).toContain('# Header');
|
|
283
|
+
expect(content).toContain('First section.');
|
|
284
|
+
expect(content).toContain('## Section A');
|
|
285
|
+
expect(content).toContain('Content A.');
|
|
286
|
+
expect(content).not.toContain('Injected Section');
|
|
287
|
+
expect(content).not.toContain('This will be removed');
|
|
288
|
+
});
|
|
289
|
+
it('should preserve content after injection', async () => {
|
|
290
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
291
|
+
await writeFile(filePath, `# Header
|
|
292
|
+
|
|
293
|
+
${INJECTION_MARKERS.start}
|
|
294
|
+
## Injected Section
|
|
295
|
+
|
|
296
|
+
This will be removed.
|
|
297
|
+
${INJECTION_MARKERS.end}
|
|
298
|
+
|
|
299
|
+
## Footer Section
|
|
300
|
+
|
|
301
|
+
Footer content.
|
|
302
|
+
`);
|
|
303
|
+
await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
304
|
+
const content = await readFile(filePath, 'utf-8');
|
|
305
|
+
expect(content).toContain('# Header');
|
|
306
|
+
expect(content).toContain('## Footer Section');
|
|
307
|
+
expect(content).toContain('Footer content.');
|
|
308
|
+
expect(content).not.toContain('Injected Section');
|
|
309
|
+
});
|
|
310
|
+
it('should handle injection in middle of file', async () => {
|
|
311
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
312
|
+
await writeFile(filePath, `# Header
|
|
313
|
+
|
|
314
|
+
Before content.
|
|
315
|
+
|
|
316
|
+
${INJECTION_MARKERS.start}
|
|
317
|
+
Injected middle.
|
|
318
|
+
${INJECTION_MARKERS.end}
|
|
319
|
+
|
|
320
|
+
After content.
|
|
321
|
+
|
|
322
|
+
## Footer
|
|
323
|
+
`);
|
|
324
|
+
await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
325
|
+
const content = await readFile(filePath, 'utf-8');
|
|
326
|
+
expect(content).toContain('Before content.');
|
|
327
|
+
expect(content).toContain('After content.');
|
|
328
|
+
expect(content).toContain('## Footer');
|
|
329
|
+
expect(content).not.toContain('Injected middle');
|
|
330
|
+
});
|
|
331
|
+
it('should work with actual CLAUDE_MD_INJECTION content', async () => {
|
|
332
|
+
const filePath = join(TEST_DIR, 'CLAUDE.md');
|
|
333
|
+
const originalContent = `# vralphy
|
|
334
|
+
|
|
335
|
+
CLI tool implementing the Ralph Playbook.
|
|
336
|
+
|
|
337
|
+
## Commands
|
|
338
|
+
|
|
339
|
+
\`\`\`bash
|
|
340
|
+
npm run build
|
|
341
|
+
npm test
|
|
342
|
+
\`\`\`
|
|
343
|
+
`;
|
|
344
|
+
await writeFile(filePath, originalContent);
|
|
345
|
+
// Inject first
|
|
346
|
+
await injectContent(filePath, CLAUDE_MD_INJECTION, INJECTION_MARKERS);
|
|
347
|
+
// Then remove
|
|
348
|
+
const result = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
349
|
+
expect(result.success).toBe(true);
|
|
350
|
+
const content = await readFile(filePath, 'utf-8');
|
|
351
|
+
expect(content).not.toContain('## vralphy Integration');
|
|
352
|
+
expect(content).not.toContain('@.vralphy/README.md');
|
|
353
|
+
// Original content preserved
|
|
354
|
+
expect(content).toContain('# vralphy');
|
|
355
|
+
expect(content).toContain('npm run build');
|
|
356
|
+
});
|
|
357
|
+
it('should handle inject-remove-inject cycle', async () => {
|
|
358
|
+
const filePath = join(TEST_DIR, 'test.md');
|
|
359
|
+
await writeFile(filePath, '# Project\n\nOriginal content.\n');
|
|
360
|
+
// Inject
|
|
361
|
+
await injectContent(filePath, '\nFirst injection\n', INJECTION_MARKERS);
|
|
362
|
+
let content = await readFile(filePath, 'utf-8');
|
|
363
|
+
expect(content).toContain('First injection');
|
|
364
|
+
// Remove
|
|
365
|
+
await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
366
|
+
content = await readFile(filePath, 'utf-8');
|
|
367
|
+
expect(content).not.toContain('First injection');
|
|
368
|
+
expect(content).toContain('Original content.');
|
|
369
|
+
// Inject again
|
|
370
|
+
await injectContent(filePath, '\nSecond injection\n', INJECTION_MARKERS);
|
|
371
|
+
content = await readFile(filePath, 'utf-8');
|
|
372
|
+
expect(content).toContain('Second injection');
|
|
373
|
+
expect(content).toContain('Original content.');
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
describe('integration with real file structures', () => {
|
|
377
|
+
it('should handle CLAUDE.md structure like the real project', async () => {
|
|
378
|
+
const filePath = join(TEST_DIR, 'CLAUDE.md');
|
|
379
|
+
// Mimic the real CLAUDE.md structure
|
|
380
|
+
await writeFile(filePath, `# vralphy
|
|
381
|
+
|
|
382
|
+
CLI tool implementing the Ralph Playbook - autonomous AI development where agents plan, build, test, and commit code.
|
|
383
|
+
|
|
384
|
+
## Commands
|
|
385
|
+
|
|
386
|
+
\`\`\`bash
|
|
387
|
+
npm run build # Compile TypeScript
|
|
388
|
+
npm run test # Run tests (vitest)
|
|
389
|
+
npm run typecheck # Type check only
|
|
390
|
+
npm run lint # ESLint
|
|
391
|
+
\`\`\`
|
|
392
|
+
|
|
393
|
+
## Architecture
|
|
394
|
+
|
|
395
|
+
- \`src/commands/\` - CLI commands (build, plan, spec, init, cleanup)
|
|
396
|
+
- \`src/lib/engines/\` - Engine abstraction (Claude, OpenCode, Codex)
|
|
397
|
+
- \`src/lib/\` - Core: config, prompts, events, slack, plan parsing
|
|
398
|
+
|
|
399
|
+
## Critical Rules
|
|
400
|
+
|
|
401
|
+
- IMPORTANT: Run \`npm run typecheck && npm test\` before committing
|
|
402
|
+
- IMPORTANT: Tests are colocated (\`*.test.ts\` next to source)
|
|
403
|
+
- IMPORTANT: Import paths use \`.js\` extension (ES modules)
|
|
404
|
+
- Use TypeScript strict mode, avoid \`any\`
|
|
405
|
+
|
|
406
|
+
## Conventions
|
|
407
|
+
|
|
408
|
+
- Async/await over callbacks
|
|
409
|
+
- Composition over inheritance
|
|
410
|
+
- Named exports preferred
|
|
411
|
+
- Silent failure for non-critical operations (slack, events)
|
|
412
|
+
|
|
413
|
+
## Detailed References
|
|
414
|
+
|
|
415
|
+
@.claude/docs/architecture.md
|
|
416
|
+
@.claude/docs/commands.md
|
|
417
|
+
@.claude/docs/engines.md
|
|
418
|
+
@.claude/docs/testing.md
|
|
419
|
+
`);
|
|
420
|
+
// Inject
|
|
421
|
+
const injectResult = await injectContent(filePath, CLAUDE_MD_INJECTION, INJECTION_MARKERS);
|
|
422
|
+
expect(injectResult.success).toBe(true);
|
|
423
|
+
let content = await readFile(filePath, 'utf-8');
|
|
424
|
+
expect(content).toContain('## vralphy Integration');
|
|
425
|
+
expect(content).toContain('## Commands'); // Original preserved
|
|
426
|
+
expect(content).toContain('## Critical Rules'); // Original preserved
|
|
427
|
+
// Remove
|
|
428
|
+
const removeResult = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
429
|
+
expect(removeResult.success).toBe(true);
|
|
430
|
+
content = await readFile(filePath, 'utf-8');
|
|
431
|
+
expect(content).not.toContain('## vralphy Integration');
|
|
432
|
+
expect(content).toContain('## Commands');
|
|
433
|
+
expect(content).toContain('## Critical Rules');
|
|
434
|
+
expect(content).toContain('@.claude/docs/architecture.md');
|
|
435
|
+
});
|
|
436
|
+
it('should handle AGENTS.md structure like the real project', async () => {
|
|
437
|
+
const filePath = join(TEST_DIR, 'AGENTS.md');
|
|
438
|
+
// Mimic the real AGENTS.md structure
|
|
439
|
+
await writeFile(filePath, `# AGENTS.md
|
|
440
|
+
|
|
441
|
+
Operational guide for AI agents working on vralphy.
|
|
442
|
+
|
|
443
|
+
## Build & Validate
|
|
444
|
+
|
|
445
|
+
\`\`\`bash
|
|
446
|
+
npm install # Install dependencies
|
|
447
|
+
npm run build # Compile TypeScript to dist/
|
|
448
|
+
npm run test # Run vitest tests
|
|
449
|
+
npm run typecheck # Type check without emit
|
|
450
|
+
npm run lint # ESLint check
|
|
451
|
+
\`\`\`
|
|
452
|
+
|
|
453
|
+
## Project Structure
|
|
454
|
+
|
|
455
|
+
\`\`\`
|
|
456
|
+
src/
|
|
457
|
+
├── index.ts # CLI entry (commander.js)
|
|
458
|
+
├── commands/ # build, plan, spec, init, cleanup
|
|
459
|
+
└── lib/
|
|
460
|
+
├── engines/ # Claude, OpenCode, Codex implementations
|
|
461
|
+
├── config.ts # Config resolution (CLI > env > file > defaults)
|
|
462
|
+
├── prompts.ts # Prompt loading & interpolation
|
|
463
|
+
├── events.ts # Event logging with rotation
|
|
464
|
+
├── slack.ts # Slack notifications (fire-and-forget)
|
|
465
|
+
└── plan.ts # IMPLEMENTATION_PLAN.md parsing
|
|
466
|
+
\`\`\`
|
|
467
|
+
|
|
468
|
+
## Tech Stack
|
|
469
|
+
|
|
470
|
+
- TypeScript (ES2022, strict mode)
|
|
471
|
+
- Node.js >= 18
|
|
472
|
+
- Commander.js for CLI
|
|
473
|
+
- Vitest for testing
|
|
474
|
+
|
|
475
|
+
## Key Patterns
|
|
476
|
+
|
|
477
|
+
- **Engine interface**: All engines implement \`Engine\` from \`base.ts\`
|
|
478
|
+
- **Config hierarchy**: CLI flags > env vars > config file > defaults
|
|
479
|
+
- **Streaming output**: Engines yield \`Chunk\` objects (text, tool_use, error, done)
|
|
480
|
+
- **Silent failures**: Logging/notifications never break execution
|
|
481
|
+
|
|
482
|
+
## Code Style
|
|
483
|
+
|
|
484
|
+
- Async/await, no callbacks
|
|
485
|
+
- Named exports, single responsibility
|
|
486
|
+
- \`_\` prefix for intentionally unused vars
|
|
487
|
+
- Import paths include \`.js\` extension
|
|
488
|
+
`);
|
|
489
|
+
// Inject
|
|
490
|
+
const injectResult = await injectContent(filePath, AGENTS_MD_INJECTION, INJECTION_MARKERS);
|
|
491
|
+
expect(injectResult.success).toBe(true);
|
|
492
|
+
let content = await readFile(filePath, 'utf-8');
|
|
493
|
+
expect(content).toContain('## vralphy');
|
|
494
|
+
expect(content).toContain('.vralphy/README.md');
|
|
495
|
+
expect(content).toContain('## Build & Validate'); // Original preserved
|
|
496
|
+
expect(content).toContain('## Tech Stack'); // Original preserved
|
|
497
|
+
// Remove
|
|
498
|
+
const removeResult = await removeInjectedContent(filePath, INJECTION_MARKERS);
|
|
499
|
+
expect(removeResult.success).toBe(true);
|
|
500
|
+
content = await readFile(filePath, 'utf-8');
|
|
501
|
+
expect(content).not.toContain(INJECTION_MARKERS.start);
|
|
502
|
+
expect(content).toContain('## Build & Validate');
|
|
503
|
+
expect(content).toContain('## Tech Stack');
|
|
504
|
+
expect(content).toContain('# AGENTS.md');
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
//# sourceMappingURL=file-injection.test.js.map
|