@hamp10/agentforge 0.2.35 → 0.2.37
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 +66 -0
- package/src/OpenClawCLI.js +54 -5
package/package.json
CHANGED
|
@@ -540,6 +540,38 @@ try {
|
|
|
540
540
|
),
|
|
541
541
|
'direct UI validation should allow standard tooling/license source comments'
|
|
542
542
|
);
|
|
543
|
+
const richExistingPageHtml = [
|
|
544
|
+
'<!doctype html><html><body><main>',
|
|
545
|
+
'<section><h1>AgentForge.ai</h1><p>' + 'Detailed product positioning and buyer rationale. '.repeat(12) + '</p></section>',
|
|
546
|
+
'<section><h2>Market Context</h2><p>' + 'Category analysis, comparable sales, and audience fit. '.repeat(10) + '</p></section>',
|
|
547
|
+
'<section><h2>Use Cases</h2><p>' + 'Platform applications, operator workflows, and strategic value. '.repeat(10) + '</p></section>',
|
|
548
|
+
'<section><h2>Acquisition</h2><p>' + 'Inquiry process, domain transfer notes, and buyer confidence. '.repeat(8) + '</p></section>',
|
|
549
|
+
'</main></body></html>',
|
|
550
|
+
].join('\n');
|
|
551
|
+
const thinReplacementPageHtml = '<!doctype html><html><body><main><section><h1>AgentForge.ai</h1><p>Short replacement.</p></section></main></body></html>';
|
|
552
|
+
assert.throws(
|
|
553
|
+
() => cli._validateDirectUiFileContent(
|
|
554
|
+
path.join(fixture.repo, 'public_html', 'domains', 'agentforge-ai.html'),
|
|
555
|
+
thinReplacementPageHtml,
|
|
556
|
+
{
|
|
557
|
+
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.',
|
|
558
|
+
oldContent: richExistingPageHtml,
|
|
559
|
+
}
|
|
560
|
+
),
|
|
561
|
+
/substantial target-page content was removed/i,
|
|
562
|
+
'clean-start permission for some scoped targets should not allow destructive content loss on a different fix-only target'
|
|
563
|
+
);
|
|
564
|
+
assert.doesNotThrow(
|
|
565
|
+
() => cli._validateDirectUiFileContent(
|
|
566
|
+
path.join(fixture.repo, 'public_html', 'domains', 'alpha.html'),
|
|
567
|
+
thinReplacementPageHtml.replace(/AgentForge\.ai/g, 'Alpha.ai'),
|
|
568
|
+
{
|
|
569
|
+
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.',
|
|
570
|
+
oldContent: richExistingPageHtml.replace(/AgentForge\.ai/g, 'Alpha.ai'),
|
|
571
|
+
}
|
|
572
|
+
),
|
|
573
|
+
'clean-start permission should still allow substantial replacement on the specific target named in the rebuild clause'
|
|
574
|
+
);
|
|
543
575
|
const protectedReplacementFile = path.join(fixture.repo, 'public_html', 'domains', 'alpha.html');
|
|
544
576
|
const protectedReplacementOriginal = readFileSync(protectedReplacementFile, 'utf-8');
|
|
545
577
|
await assert.rejects(
|
|
@@ -636,6 +668,40 @@ try {
|
|
|
636
668
|
} finally {
|
|
637
669
|
rmSync(linkedWorkspace, { recursive: true, force: true });
|
|
638
670
|
}
|
|
671
|
+
const knownProjectsRoot = mkdtempSync(path.join(tmpdir(), 'agentforge-known-projects-'));
|
|
672
|
+
const knownAgentWorkspace = mkdtempSync(path.join(tmpdir(), 'agentforge-known-agent-workspace-'));
|
|
673
|
+
try {
|
|
674
|
+
const knownProject = path.join(knownProjectsRoot, 'Hamp.com');
|
|
675
|
+
const knownDomainsDir = path.join(knownProject, 'public_html', 'domains');
|
|
676
|
+
mkdirSync(knownDomainsDir, { recursive: true });
|
|
677
|
+
writeFileSync(path.join(knownDomainsDir, 'agentforge-ai.html'), '<!doctype html><html><body><h1>AgentForge.ai</h1></body></html>');
|
|
678
|
+
writeFileSync(path.join(knownDomainsDir, 'adgenius-ai.html'), '<!doctype html><html><body><h1>AdGenius.ai</h1></body></html>');
|
|
679
|
+
symlinkSync(knownProject, path.join(knownAgentWorkspace, 'Hamp.com'), 'dir');
|
|
680
|
+
writeFileSync(path.join(knownAgentWorkspace, 'AGENTFORGE.md'), '# AgentForge.ai platform notes');
|
|
681
|
+
const knownProjectCli = Object.create(OpenClawCLI.prototype);
|
|
682
|
+
knownProjectCli._knownProjectsRoots = () => [knownProjectsRoot];
|
|
683
|
+
const mixedKnownProjectTask = 'Can you make Hamp.com listing pages for AgentBoard.ai and Scrimmage.ai from scratch, and fix the AgentForge.ai listing page?';
|
|
684
|
+
assert.doesNotThrow(
|
|
685
|
+
() => knownProjectCli._guardDirectFileWritePath(
|
|
686
|
+
path.join(knownAgentWorkspace, 'Hamp.com', 'public_html', 'domains', 'agentboard-ai.html'),
|
|
687
|
+
knownAgentWorkspace,
|
|
688
|
+
{ task: mixedKnownProjectTask }
|
|
689
|
+
),
|
|
690
|
+
'agent-workspace root files such as AGENTFORGE.md should not count as scoped project pages when the target resolves into a known project'
|
|
691
|
+
);
|
|
692
|
+
assert.throws(
|
|
693
|
+
() => knownProjectCli._guardDirectFileWritePath(
|
|
694
|
+
path.join(knownAgentWorkspace, 'Hamp.com', 'Hamp.com', 'public_html', 'domains', 'agentboard-ai.html'),
|
|
695
|
+
knownAgentWorkspace,
|
|
696
|
+
{ task: mixedKnownProjectTask }
|
|
697
|
+
),
|
|
698
|
+
/nested duplicate of the current project/i,
|
|
699
|
+
'known-project symlink writes should still reject same-name nested project-copy paths from temp agent workspaces'
|
|
700
|
+
);
|
|
701
|
+
} finally {
|
|
702
|
+
rmSync(knownProjectsRoot, { recursive: true, force: true });
|
|
703
|
+
rmSync(knownAgentWorkspace, { recursive: true, force: true });
|
|
704
|
+
}
|
|
639
705
|
assert.equal(
|
|
640
706
|
cli._directScopeFileCandidates(fixture.repo, ['gamma']).some(candidate => candidate.startsWith(`${path.basename(fixture.repo)}/`)),
|
|
641
707
|
false,
|
package/src/OpenClawCLI.js
CHANGED
|
@@ -415,7 +415,28 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
415
415
|
|
|
416
416
|
_projectsRootForPath(candidate) {
|
|
417
417
|
if (!candidate) return null;
|
|
418
|
-
|
|
418
|
+
const realCandidate = this._realPathForMaybeMissingPath(candidate);
|
|
419
|
+
return this._knownProjectsRoots().find(root => {
|
|
420
|
+
if (!existsSync(root)) return false;
|
|
421
|
+
const realRoot = this._realPathForMaybeMissingPath(root);
|
|
422
|
+
return this._isPathInside(realCandidate, realRoot);
|
|
423
|
+
}) || null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
_projectDirForKnownProjectPath(candidate) {
|
|
427
|
+
if (!candidate) return null;
|
|
428
|
+
const realCandidate = this._realPathForMaybeMissingPath(candidate);
|
|
429
|
+
const projectsRoot = this._projectsRootForPath(realCandidate);
|
|
430
|
+
if (!projectsRoot) return null;
|
|
431
|
+
let realProjectsRoot = projectsRoot;
|
|
432
|
+
try {
|
|
433
|
+
realProjectsRoot = realpathSync(projectsRoot);
|
|
434
|
+
} catch { /* non-fatal */ }
|
|
435
|
+
const relative = path.relative(realProjectsRoot, realCandidate);
|
|
436
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
437
|
+
const [projectName] = relative.split(path.sep).filter(Boolean);
|
|
438
|
+
if (!projectName) return null;
|
|
439
|
+
return path.join(realProjectsRoot, projectName);
|
|
419
440
|
}
|
|
420
441
|
|
|
421
442
|
_isProjectScopedWorkDir(workDir) {
|
|
@@ -988,7 +1009,14 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
988
1009
|
}
|
|
989
1010
|
|
|
990
1011
|
_guardDirectFileWritePath(filePath, workDir, options = {}) {
|
|
991
|
-
|
|
1012
|
+
const realFilePath = this._realPathForMaybeMissingPath(filePath);
|
|
1013
|
+
const projectDirForFile = this._projectDirForKnownProjectPath(realFilePath);
|
|
1014
|
+
if (projectDirForFile && this._isPathInside(realFilePath, projectDirForFile)) {
|
|
1015
|
+
filePath = realFilePath;
|
|
1016
|
+
workDir = projectDirForFile;
|
|
1017
|
+
} else {
|
|
1018
|
+
filePath = this._canonicalizeLinkedProjectPath(filePath, workDir);
|
|
1019
|
+
}
|
|
992
1020
|
workDir = this._realPathForMaybeMissingPath(workDir);
|
|
993
1021
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options.task);
|
|
994
1022
|
const relative = path.relative(workDir, filePath);
|
|
@@ -1165,7 +1193,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1165
1193
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1166
1194
|
if (!/\.css$/i.test(String(filePath || ''))) return;
|
|
1167
1195
|
const taskText = String(options?.task || '');
|
|
1168
|
-
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);
|
|
1169
1197
|
if (!isCleanStartTask) return;
|
|
1170
1198
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options?.task);
|
|
1171
1199
|
if (!pageOnly || slugs.length === 0) return;
|
|
@@ -1261,7 +1289,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1261
1289
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1262
1290
|
if (!/\.(?:html?|xhtml|astro|mdx?)$/i.test(String(filePath || ''))) return;
|
|
1263
1291
|
const taskText = String(options?.task || '');
|
|
1264
|
-
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);
|
|
1265
1293
|
if (!isCleanStartTask) return;
|
|
1266
1294
|
const { slugs, pageOnly } = this._extractDirectExplicitScope(options?.task);
|
|
1267
1295
|
if (!pageOnly || slugs.length === 0) return;
|
|
@@ -1323,6 +1351,27 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1323
1351
|
throw err;
|
|
1324
1352
|
}
|
|
1325
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
|
+
|
|
1326
1375
|
_validateDirectUiImplementationArtifacts(filePath, content, options = {}) {
|
|
1327
1376
|
if (!this._isDirectBroadUiQualityTask(options?.task)) return;
|
|
1328
1377
|
if (!/\.(?:html?|xhtml|css|s[ac]ss|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(String(filePath || ''))) return;
|
|
@@ -1399,7 +1448,7 @@ export class OpenClawCLI extends EventEmitter {
|
|
|
1399
1448
|
}
|
|
1400
1449
|
}
|
|
1401
1450
|
|
|
1402
|
-
const taskAllowsContentReduction =
|
|
1451
|
+
const taskAllowsContentReduction = this._directTaskAllowsContentReductionForPath(filePath, taskText);
|
|
1403
1452
|
if (!taskAllowsContentReduction && (oldText.trim() || headText.trim()) && /\.(?:html?|xhtml|jsx?|tsx?|vue|svelte|astro|mdx?)$/i.test(String(filePath || ''))) {
|
|
1404
1453
|
const visibleTextApprox = (value) => String(value || '')
|
|
1405
1454
|
.replace(/<script\b[\s\S]*?<\/script>/gi, ' ')
|