@hamp10/agentforge 0.2.27 → 0.2.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hamp10/agentforge",
3
- "version": "0.2.27",
3
+ "version": "0.2.28",
4
4
  "description": "AgentForge worker — connect your machine to agentforge.ai",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,6 +35,12 @@ const cases = [
35
35
  absent: ['example-com', 'delete', 'rebuild', 'readability', 'design', 'urls', 'site'],
36
36
  pageOnly: true,
37
37
  },
38
+ {
39
+ text: 'Delete and rebuild the listing pages for Alpha and Beta from a clean start. Only change those listing pages.',
40
+ slugs: ['alpha', 'beta'],
41
+ absent: ['beta-from', 'clean-start', 'from'],
42
+ pageOnly: true,
43
+ },
38
44
  {
39
45
  text: 'Work on Alpha pages for readability',
40
46
  slugs: ['alpha'],
@@ -214,6 +220,25 @@ try {
214
220
  const worker = Object.create(AgentForgeWorker.prototype);
215
221
  worker.cli = { isAnthropicApiKey: (key) => /^sk-ant-/i.test(String(key || '')) };
216
222
  const cli = Object.create(OpenClawCLI.prototype);
223
+ const deletedScopedPagePath = path.join(fixture.repo, 'public_html', 'domains', 'alpha.html');
224
+ rmSync(deletedScopedPagePath, { force: true });
225
+ assert.equal(
226
+ worker._buildDeletedScopedPageNudge(
227
+ [{ root: fixture.repo, head: fixture.head }],
228
+ 'Delete the Example.com listing page for Alpha. Only change that listing page.'
229
+ ),
230
+ '',
231
+ 'explicit page deletion tasks should be allowed to leave the scoped target page deleted'
232
+ );
233
+ assert.match(
234
+ worker._buildDeletedScopedPageNudge(
235
+ [{ root: fixture.repo, head: fixture.head }],
236
+ 'Delete and rebuild the Alpha listing page from a clean start, preserving the same URL. Only change that listing page.'
237
+ ),
238
+ /deleted target page source\(s\) still missing/i,
239
+ 'clean-start rebuild tasks should still require the scoped target page to be recreated'
240
+ );
241
+ git(fixture.repo, ['restore', '--', 'public_html/domains/alpha.html']);
217
242
  const projectsRoot = mkdtempSync(path.join(tmpdir(), 'agentforge-project-list-'));
218
243
  let agentWorkspace = null;
219
244
  try {
@@ -112,7 +112,7 @@ export function extractNamedPageScopeSlugs(message) {
112
112
  addScopeCandidate(candidates, match[1]);
113
113
  }
114
114
 
115
- const afterPage = /\b(?:pages?|screens?|routes?|views?|listings?)\s+(?:for|of|called|named)\s+(?:the\s+)?([a-z0-9][a-z0-9 ._/-]{0,160}?)(?=$|[?!;]|\.(?:\s|$)|\s+(?:on|at|in|with|without|that|which|where|when|because|so|but)\b)/gi;
115
+ const afterPage = /\b(?:pages?|screens?|routes?|views?|listings?)\s+(?:for|of|called|named)\s+(?:the\s+)?([a-z0-9][a-z0-9 ._/-]{0,160}?)(?=$|[?!;]|\.(?:\s|$)|\s+(?:on|at|in|from|with|without|while|using|preserv(?:e|ing)|that|which|where|when|because|so|but)\b)/gi;
116
116
  for (const match of text.matchAll(afterPage)) {
117
117
  const before = text.slice(Math.max(0, match.index - 120), match.index);
118
118
  const precedingToken = before.match(/\b([a-z0-9][a-z0-9._/-]{2,})\s*$/i)?.[1] || '';
@@ -124,7 +124,7 @@ export function extractNamedPageScopeSlugs(message) {
124
124
 
125
125
  for (const match of text.matchAll(/\b[a-z0-9]+(?:[.-][a-z0-9]+)+\b/gi)) {
126
126
  const after = text.slice(match.index + match[0].length);
127
- const pageTarget = after.match(/^\s+(?:listing\s+)?(?:pages?|screens?|routes?|views?|listings?)\s+(?:for|of|called|named)\s+(?:the\s+)?([a-z0-9][a-z0-9 ._/-]{0,160}?)(?=$|[?!;]|\.(?:\s|$)|\s+(?:on|at|in|with|without|that|which|where|when|because|so|but)\b)/i);
127
+ const pageTarget = after.match(/^\s+(?:listing\s+)?(?:pages?|screens?|routes?|views?|listings?)\s+(?:for|of|called|named)\s+(?:the\s+)?([a-z0-9][a-z0-9 ._/-]{0,160}?)(?=$|[?!;]|\.(?:\s|$)|\s+(?:on|at|in|from|with|without|while|using|preserv(?:e|ing)|that|which|where|when|because|so|but)\b)/i);
128
128
  if (pageTarget) {
129
129
  const targetCandidates = new Set();
130
130
  addScopeCandidate(targetCandidates, pageTarget[1]);
package/src/worker.js CHANGED
@@ -501,6 +501,15 @@ export class AgentForgeWorker extends EventEmitter {
501
501
  return /\b(delete|remove|drop|strip|simplify|replace|rewrite|rebuild|from scratch|start over|nuke|wipe|clear out|throw away)\b/i.test(String(userMessage || ''));
502
502
  }
503
503
 
504
+ _allowsScopedPageSourcesToRemainDeleted(userMessage) {
505
+ const text = String(userMessage || '');
506
+ const asksForDeletion = /\b(delete|remove|drop|unpublish|decommission|take down|take offline)\b/i.test(text);
507
+ if (!asksForDeletion) return false;
508
+
509
+ const asksForRebuildOrRepair = /\b(rebuild|recreate|remake|rewrite|replace|restore|redesign|build|create|implement|fix|improve|polish|repair|start over|from scratch|clean start|fresh start|same urls?|same paths?|preserv(?:e|ing) the same urls?)\b/i.test(text);
510
+ return !asksForRebuildOrRepair;
511
+ }
512
+
504
513
  _parseNumstat(output, source) {
505
514
  const stats = [];
506
515
  for (const line of String(output || '').split('\n')) {
@@ -848,6 +857,7 @@ export class AgentForgeWorker extends EventEmitter {
848
857
 
849
858
  _findDeletedScopedPageSources(repoBaselines, userMessage) {
850
859
  if (!Array.isArray(repoBaselines) || repoBaselines.length === 0) return [];
860
+ if (this._allowsScopedPageSourcesToRemainDeleted(userMessage)) return [];
851
861
  const { slugs: allowedSlugs, pageOnly } = this._extractExplicitScope(userMessage);
852
862
  if (allowedSlugs.length === 0 || !pageOnly) return [];
853
863