@hamp10/agentforge 0.2.36 → 0.2.38
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 +1 -1
- package/scripts/check-task-semantics.js +65 -0
- package/src/OpenClawCLI.js +24 -3
- package/src/taskSemantics.js +3 -2
- package/src/worker.js +22 -5
package/package.json
CHANGED
|
@@ -35,6 +35,12 @@ const cases = [
|
|
|
35
35
|
absent: ['example-com', 'make', 'fix'],
|
|
36
36
|
pageOnly: true,
|
|
37
37
|
},
|
|
38
|
+
{
|
|
39
|
+
text: 'Work on the Example.com listing pages for AlphaBoard.ai, BetaMatch.ai, and GammaForge.ai. Make all three pages visually polished and consistent with the rest of the site.',
|
|
40
|
+
slugs: ['alphaboard-ai', 'betamatch-ai', 'gammaforge-ai'],
|
|
41
|
+
absent: ['example-com', 'three', 'all-three', 'visual', 'polished'],
|
|
42
|
+
pageOnly: true,
|
|
43
|
+
},
|
|
38
44
|
{
|
|
39
45
|
text: 'Work on the Example.com listing pages for AlphaBoard and BetaMatch. Delete and rebuild those two listing page implementations from a clean start, preserving the same URLs and site conventions. Fix the readability and design issues. Only change those two listing pages.',
|
|
40
46
|
slugs: ['alphaboard', 'betamatch'],
|
|
@@ -249,6 +255,33 @@ try {
|
|
|
249
255
|
'clean-start rebuild tasks should still require the scoped target page to be recreated'
|
|
250
256
|
);
|
|
251
257
|
git(fixture.repo, ['restore', '--', 'public_html/domains/alpha.html']);
|
|
258
|
+
const nestedAlphaRel = `${path.basename(fixture.repo)}/public_html/domains/alpha.html`;
|
|
259
|
+
rmSync(path.join(fixture.repo, nestedAlphaRel), { force: true });
|
|
260
|
+
assert.equal(
|
|
261
|
+
worker._buildDeletedScopedPageNudge(
|
|
262
|
+
[{ root: fixture.repo, head: fixture.head }],
|
|
263
|
+
'Delete and rebuild the Alpha listing page from a clean start, preserving the same URL. Only change that listing page.'
|
|
264
|
+
),
|
|
265
|
+
'',
|
|
266
|
+
'tracked same-name nested project copies should not count as missing scoped page sources'
|
|
267
|
+
);
|
|
268
|
+
assert.equal(
|
|
269
|
+
worker._scopeAllowsChangedPath(
|
|
270
|
+
{ root: fixture.repo, head: fixture.head },
|
|
271
|
+
nestedAlphaRel,
|
|
272
|
+
['alpha'],
|
|
273
|
+
true,
|
|
274
|
+
'Delete and rebuild the Alpha listing page from a clean start. Only change that listing page.'
|
|
275
|
+
),
|
|
276
|
+
false,
|
|
277
|
+
'tracked same-name nested project copies should be treated as out-of-scope changes'
|
|
278
|
+
);
|
|
279
|
+
assert.equal(
|
|
280
|
+
worker._allCurrentPageSourceFiles(fixture.repo).includes(nestedAlphaRel),
|
|
281
|
+
false,
|
|
282
|
+
'tracked same-name nested project copies should not pollute current page source discovery'
|
|
283
|
+
);
|
|
284
|
+
git(fixture.repo, ['restore', '--', nestedAlphaRel]);
|
|
252
285
|
const projectsRoot = mkdtempSync(path.join(tmpdir(), 'agentforge-project-list-'));
|
|
253
286
|
let agentWorkspace = null;
|
|
254
287
|
try {
|
|
@@ -540,6 +573,38 @@ try {
|
|
|
540
573
|
),
|
|
541
574
|
'direct UI validation should allow standard tooling/license source comments'
|
|
542
575
|
);
|
|
576
|
+
const richExistingPageHtml = [
|
|
577
|
+
'<!doctype html><html><body><main>',
|
|
578
|
+
'<section><h1>AgentForge.ai</h1><p>' + 'Detailed product positioning and buyer rationale. '.repeat(12) + '</p></section>',
|
|
579
|
+
'<section><h2>Market Context</h2><p>' + 'Category analysis, comparable sales, and audience fit. '.repeat(10) + '</p></section>',
|
|
580
|
+
'<section><h2>Use Cases</h2><p>' + 'Platform applications, operator workflows, and strategic value. '.repeat(10) + '</p></section>',
|
|
581
|
+
'<section><h2>Acquisition</h2><p>' + 'Inquiry process, domain transfer notes, and buyer confidence. '.repeat(8) + '</p></section>',
|
|
582
|
+
'</main></body></html>',
|
|
583
|
+
].join('\n');
|
|
584
|
+
const thinReplacementPageHtml = '<!doctype html><html><body><main><section><h1>AgentForge.ai</h1><p>Short replacement.</p></section></main></body></html>';
|
|
585
|
+
assert.throws(
|
|
586
|
+
() => cli._validateDirectUiFileContent(
|
|
587
|
+
path.join(fixture.repo, 'public_html', 'domains', 'agentforge-ai.html'),
|
|
588
|
+
thinReplacementPageHtml,
|
|
589
|
+
{
|
|
590
|
+
task: 'Work on the Example.com listing pages for Alpha.ai, Beta.ai, and AgentForge.ai. Delete and rebuild Alpha.ai and Beta.ai from a clean start, and fix AgentForge.ai so all three are visually polished.',
|
|
591
|
+
oldContent: richExistingPageHtml,
|
|
592
|
+
}
|
|
593
|
+
),
|
|
594
|
+
/substantial target-page content was removed/i,
|
|
595
|
+
'clean-start permission for some scoped targets should not allow destructive content loss on a different fix-only target'
|
|
596
|
+
);
|
|
597
|
+
assert.doesNotThrow(
|
|
598
|
+
() => cli._validateDirectUiFileContent(
|
|
599
|
+
path.join(fixture.repo, 'public_html', 'domains', 'alpha.html'),
|
|
600
|
+
thinReplacementPageHtml.replace(/AgentForge\.ai/g, 'Alpha.ai'),
|
|
601
|
+
{
|
|
602
|
+
task: 'Work on the Example.com listing pages for Alpha.ai, Beta.ai, and AgentForge.ai. Delete and rebuild Alpha.ai and Beta.ai from a clean start, and fix AgentForge.ai so all three are visually polished.',
|
|
603
|
+
oldContent: richExistingPageHtml.replace(/AgentForge\.ai/g, 'Alpha.ai'),
|
|
604
|
+
}
|
|
605
|
+
),
|
|
606
|
+
'clean-start permission should still allow substantial replacement on the specific target named in the rebuild clause'
|
|
607
|
+
);
|
|
543
608
|
const protectedReplacementFile = path.join(fixture.repo, 'public_html', 'domains', 'alpha.html');
|
|
544
609
|
const protectedReplacementOriginal = readFileSync(protectedReplacementFile, 'utf-8');
|
|
545
610
|
await assert.rejects(
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -1193,7 +1193,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1193
1193
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1194
1194
|
if (!/\.css$/i.test(String(filePath || ''))) return;
|
|
1195
1195
|
const taskText = String(options?.task || '');
|
|
1196
|
-
const isCleanStartTask = /\b(?:delete|rebuild|clean start|from scratch|start over|fresh|remake)\b/i
|
|
1196
|
+
const isCleanStartTask = this._directTaskAllowsContentReductionForPath(filePath, taskText, /\b(?:delete|rebuild|clean start|from scratch|start over|fresh|remake)\b/i);
|
|
1197
1197
|
if (!isCleanStartTask) return;
|
|
1198
1198
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options?.task);
|
|
1199
1199
|
if (!pageOnly || slugs.length === 0) return;
|
|
@@ -1289,7 +1289,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1289
1289
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1290
1290
|
if (!/\.(?:html?|xhtml|astro|mdx?)$/i.test(String(filePath || ''))) return;
|
|
1291
1291
|
const taskText = String(options?.task || '');
|
|
1292
|
-
const isCleanStartTask = /\b(?:delete|rebuild|clean start|from scratch|start over|fresh|remake)\b/i
|
|
1292
|
+
const isCleanStartTask = this._directTaskAllowsContentReductionForPath(filePath, taskText, /\b(?:delete|rebuild|clean start|from scratch|start over|fresh|remake)\b/i);
|
|
1293
1293
|
if (!isCleanStartTask) return;
|
|
1294
1294
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options?.task);
|
|
1295
1295
|
if (!pageOnly || slugs.length === 0) return;
|
|
@@ -1351,6 +1351,27 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1351
1351
|
throw err;
|
|
1352
1352
|
}
|
|
1353
1353
|
|
|
1354
|
+
_directTaskActionClauses(taskText) {
|
|
1355
|
+
return String(taskText || '')
|
|
1356
|
+
.split(/(?:[.!?;]\s+|,\s*(?:but|then)\s+|,\s*and\s+(?=(?:fix|repair|update|improve|polish|address|adjust|correct)\b)|\band\s+(?=(?:fix|repair|update|improve|polish|address|adjust|correct)\b))/i)
|
|
1357
|
+
.map(clause => clause.trim())
|
|
1358
|
+
.filter(Boolean);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
_directTaskAllowsContentReductionForPath(filePath, taskText, actionRe = /\b(?:remove|delete|strip|simplif(?:y|ied|ication)|shorten|condense|reduce|minimal|less content|cleanup|clean up|prune)\b/i) {
|
|
1362
|
+
const text = String(taskText || '');
|
|
1363
|
+
if (!actionRe.test(text)) return false;
|
|
1364
|
+
const { slugs, pageOnly } = this._extractDirectExplicitScope(text);
|
|
1365
|
+
if (!pageOnly || slugs.length <= 1) return true;
|
|
1366
|
+
const matchingSlugs = this._directScopeSlugsMatchingText(String(filePath || '').toLowerCase(), slugs);
|
|
1367
|
+
if (matchingSlugs.length === 0) return false;
|
|
1368
|
+
const clauses = this._directTaskActionClauses(text);
|
|
1369
|
+
return matchingSlugs.some(slug => clauses.some(clause =>
|
|
1370
|
+
actionRe.test(clause) &&
|
|
1371
|
+
this._directScopeSlugsMatchingText(clause.toLowerCase(), [slug]).length > 0
|
|
1372
|
+
));
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1354
1375
|
_validateDirectUiImplementationArtifacts(filePath, content, options = {}) {
|
|
1355
1376
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1356
1377
|
if (!/\.(?:html?|xhtml|css|s[ac]ss|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(String(filePath || ''))) return;
|
|
@@ -1427,7 +1448,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1427
1448
|
}
|
|
1428
1449
|
}
|
|
1429
1450
|
|
|
1430
|
-
const taskAllowsContentReduction =
|
|
1451
|
+
const taskAllowsContentReduction = this._directTaskAllowsContentReductionForPath(filePath, taskText);
|
|
1431
1452
|
if (!taskAllowsContentReduction && (oldText.trim() || headText.trim()) && /\.(?:html?|xhtml|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(String(filePath || ''))) {
|
|
1432
1453
|
const visibleTextApprox = (value) => String(value || '')
|
|
1433
1454
|
.replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
|
package/src/taskSemantics.js
CHANGED
|
@@ -11,8 +11,9 @@ const GENERIC_SCOPE_WORDS = new Set([
|
|
|
11
11
|
'website', 'app', 'readability', 'readable', 'legibility', 'contrast',
|
|
12
12
|
'quality', 'visual', 'content', 'layout', 'styling', 'polish',
|
|
13
13
|
'current', 'target', 'targets', 'requested', 'same', 'rest', 'live',
|
|
14
|
-
'local', 'itself', 'only', 'those', 'these', 'this', 'that',
|
|
15
|
-
'
|
|
14
|
+
'local', 'itself', 'only', 'all', 'those', 'these', 'this', 'that',
|
|
15
|
+
'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine',
|
|
16
|
+
'ten', 'both', 'change', 'edit', 'modify', 'touch', 'make', 'improve', 'fix',
|
|
16
17
|
'update', 'redesign', 'owned', 'source', 'style', 'styles', 'global',
|
|
17
18
|
'shared', 'reference', 'references', 'broaden', 'work', 'wrong', 'bad',
|
|
18
19
|
'broken',
|
package/src/worker.js
CHANGED
|
@@ -650,6 +650,22 @@ export class AgentForgeWorker extends EventEmitter {
|
|
|
650
650
|
return /\.(?:html?|xhtml|astro|mdx?)$/i.test(String(relativePath || ''));
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
+
_nestedProjectCopyCanonicalRel(repo, rel) {
|
|
654
|
+
const normalized = String(rel || '').replace(/^[/\\]+/, '');
|
|
655
|
+
if (!repo || !normalized) return '';
|
|
656
|
+
const parts = normalized.split(/[\\/]+/).filter(Boolean);
|
|
657
|
+
if (parts.length < 2 || parts[0].toLowerCase() !== path.basename(repo).toLowerCase()) return '';
|
|
658
|
+
return parts.slice(1).join('/');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
_isNestedProjectCopyRel(repo, rel, ref = 'HEAD') {
|
|
662
|
+
const canonicalRel = this._nestedProjectCopyCanonicalRel(repo, rel);
|
|
663
|
+
if (!canonicalRel) return false;
|
|
664
|
+
const canonicalAbs = path.join(repo, ...canonicalRel.split('/'));
|
|
665
|
+
if (existsSync(canonicalAbs) || existsSync(path.dirname(canonicalAbs))) return true;
|
|
666
|
+
return this._gitPathExistsAtRef(repo, ref || 'HEAD', canonicalRel);
|
|
667
|
+
}
|
|
668
|
+
|
|
653
669
|
_isBroadReferenceSourcePath(relativePath) {
|
|
654
670
|
const normalized = String(relativePath || '').replace(/\\/g, '/').toLowerCase();
|
|
655
671
|
const parts = normalized.split('/').filter(Boolean);
|
|
@@ -727,6 +743,7 @@ export class AgentForgeWorker extends EventEmitter {
|
|
|
727
743
|
const names = new Set();
|
|
728
744
|
const add = (output) => {
|
|
729
745
|
for (const rel of String(output || '').split('\n').map(line => line.trim()).filter(Boolean)) {
|
|
746
|
+
if (this._isNestedProjectCopyRel(repo, rel)) continue;
|
|
730
747
|
if (this._isPageSourcePath(rel)) names.add(rel);
|
|
731
748
|
}
|
|
732
749
|
};
|
|
@@ -785,6 +802,7 @@ export class AgentForgeWorker extends EventEmitter {
|
|
|
785
802
|
|
|
786
803
|
_scopeAllowsChangedPath(baseline, rel, allowedSlugs, pageOnly, userMessage = '') {
|
|
787
804
|
const lower = String(rel || '').toLowerCase();
|
|
805
|
+
if (this._isNestedProjectCopyRel(baseline?.root, rel, baseline?.head || 'HEAD')) return false;
|
|
788
806
|
const slugAllowed = this._scopeSlugsMatchingText(lower, allowedSlugs).length > 0;
|
|
789
807
|
if (slugAllowed) {
|
|
790
808
|
if (this._isNewHtmlPageOutsideExistingCollection(baseline, rel, allowedSlugs, pageOnly)) return false;
|
|
@@ -988,13 +1006,12 @@ export class AgentForgeWorker extends EventEmitter {
|
|
|
988
1006
|
const deleted = [];
|
|
989
1007
|
const status = this._gitStatusPorcelain(baseline.root, 10000);
|
|
990
1008
|
for (const rawLine of String(status || '').split('\n').filter(Boolean)) {
|
|
991
|
-
const
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const rel = rawLine.length >= 3 && rawLine[2] === ' ' ? rawLine.slice(3) : rawLine.trim().replace(/^..\s+/, '');
|
|
995
|
-
const pathName = rel.includes(' -> ') ? rel.split(' -> ').pop() : rel;
|
|
1009
|
+
const statusCode = rawLine.slice(0, 2);
|
|
1010
|
+
if (!statusCode.includes('D')) continue;
|
|
1011
|
+
const pathName = this._parseGitStatusPaths(rawLine)[0] || '';
|
|
996
1012
|
const lower = String(pathName || '').toLowerCase();
|
|
997
1013
|
if (!this._isPageSourcePath(lower)) continue;
|
|
1014
|
+
if (this._isNestedProjectCopyRel(baseline.root, pathName, baseline.head || 'HEAD')) continue;
|
|
998
1015
|
if (this._scopeSlugsMatchingText(lower, allowedSlugs).length === 0) continue;
|
|
999
1016
|
deleted.push(pathName);
|
|
1000
1017
|
}
|