@lazyingart/agintiflow 0.20.291 → 0.20.292
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/docs/supervision-campaign-ledger.md +39 -0
- package/package.json +1 -1
- package/scripts/smoke-coding-tools.js +39 -0
- package/scripts/smoke-dynamic-step-budget.js +85 -0
- package/scripts/smoke-model-roles.js +43 -1
- package/scripts/smoke-skills.js +6 -0
- package/scripts/smoke-workspace-secret-false-positive.js +20 -0
- package/skills/security-review/SKILL.md +2 -0
- package/src/permission-advice.js +8 -1
- package/src/redaction.js +1 -1
- package/src/scs-evidence.js +46 -3
- package/src/workspace-tools.js +1 -1
|
@@ -431,3 +431,42 @@ punctuation search literal, added regression coverage, passed four unit tests,
|
|
|
431
431
|
and committed a clean tree at `b956470`. The independent migration-safety
|
|
432
432
|
contract also passed. The complete npm suite passes, and the runtime fix is
|
|
433
433
|
released in AgInTiFlow `0.20.291`.
|
|
434
|
+
|
|
435
|
+
### Explicit test evidence and security-review completion
|
|
436
|
+
|
|
437
|
+
`security-labshare-050` asked a fresh DeepSeek Pro session to harden a small
|
|
438
|
+
standard-library laboratory service from an imperfect, outcome-level prompt.
|
|
439
|
+
The agent removed a default credential, bounded artifact and dataset paths,
|
|
440
|
+
kept export execution shell-free, redacted audit credentials, protected the
|
|
441
|
+
public status response, added focused tests, and committed a clean repair. The
|
|
442
|
+
hidden acceptance contract then exposed a remaining audit-log injection path:
|
|
443
|
+
newlines in an actor or artifact field could forge additional physical records.
|
|
444
|
+
|
|
445
|
+
During the retained-session repair, AgInTi fixed the production source but
|
|
446
|
+
committed it before adding the explicitly requested regression test. This was a
|
|
447
|
+
runtime contract defect, not a model-quality failure. The mutation parser did
|
|
448
|
+
not recognize contextual requests such as "add a regression test," and the SCS
|
|
449
|
+
evidence contract did not convert "run the tests" into a fresh test obligation.
|
|
450
|
+
Consequently, source and Git evidence could satisfy the phase too early.
|
|
451
|
+
|
|
452
|
+
AgInTiFlow now recognizes explicit English and Chinese test-file mutations,
|
|
453
|
+
requires fresh test evidence for explicit run/rerun-test requests, and keeps
|
|
454
|
+
task-owned commit completion closed until that evidence exists. The regression
|
|
455
|
+
suite reproduces the stale persisted contract and proves no commit is offered
|
|
456
|
+
after source mutation but before a fresh passing test. It avoids broad keyword
|
|
457
|
+
matching, so phrases such as "create a canvas preview for this smoke test" do
|
|
458
|
+
not invent a test-file mutation.
|
|
459
|
+
|
|
460
|
+
The same release accepts genuinely observational Git evidence when no
|
|
461
|
+
consequential Git action is required, permits only the exact bounded Python
|
|
462
|
+
cache-cleanup forms used by project hygiene, and treats masked values such as
|
|
463
|
+
`token=***` as safe status evidence without weakening detection of real
|
|
464
|
+
credential assignments. The security-review skill now covers control-character
|
|
465
|
+
log injection and requires a standalone note to distinguish deployment
|
|
466
|
+
boundary, threat model, controls, residual risks, non-goals, and verification.
|
|
467
|
+
|
|
468
|
+
The retained session completed `SECURITY.md` after a valid secret-content
|
|
469
|
+
block, passed the exact hidden contract and all 13 visible tests, and left a
|
|
470
|
+
clean target repository at `ef3c099`. Focused regressions and the complete npm
|
|
471
|
+
suite pass. These runtime and skill fixes are released in AgInTiFlow
|
|
472
|
+
`0.20.292`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.292",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -1680,6 +1680,45 @@ try {
|
|
|
1680
1680
|
mixedValidationCleanupAdvice.autoRecover === true,
|
|
1681
1681
|
"unrequested cleanup should be skipped without pausing substantive work"
|
|
1682
1682
|
);
|
|
1683
|
+
const pythonCacheCleanupArgs = {
|
|
1684
|
+
command:
|
|
1685
|
+
"find . -type d -name __pycache__ -prune -exec rm -rf {} + ; find . -type f -name '*.pyc' -delete; git status --short",
|
|
1686
|
+
};
|
|
1687
|
+
assert(
|
|
1688
|
+
isUnrequestedCleanupCommand(
|
|
1689
|
+
"run_command",
|
|
1690
|
+
pythonCacheCleanupArgs,
|
|
1691
|
+
{ goal: "Repair the service, run its tests, and commit the intentional work." },
|
|
1692
|
+
{}
|
|
1693
|
+
),
|
|
1694
|
+
"post-acceptance Python cache deletion was not recognized as optional housekeeping"
|
|
1695
|
+
);
|
|
1696
|
+
const pythonCacheCleanupAdvice = buildPermissionAdvice({
|
|
1697
|
+
toolName: "run_command",
|
|
1698
|
+
args: pythonCacheCleanupArgs,
|
|
1699
|
+
guard: {
|
|
1700
|
+
category: "destructive",
|
|
1701
|
+
reason: "Destructive shell commands require Allow destructive actions.",
|
|
1702
|
+
},
|
|
1703
|
+
config: {
|
|
1704
|
+
...dockerWorkspacePolicy,
|
|
1705
|
+
goal: "Repair the service, run its tests, and commit the intentional work.",
|
|
1706
|
+
},
|
|
1707
|
+
state: { sessionId: "coding-python-cache-cleanup-smoke" },
|
|
1708
|
+
});
|
|
1709
|
+
assert(
|
|
1710
|
+
pythonCacheCleanupAdvice.autoRecover === true,
|
|
1711
|
+
"ignored Python cache cleanup should be skipped without pausing a completed task"
|
|
1712
|
+
);
|
|
1713
|
+
assert(
|
|
1714
|
+
!isUnrequestedCleanupCommand(
|
|
1715
|
+
"run_command",
|
|
1716
|
+
{ command: "find . -type f -delete" },
|
|
1717
|
+
{ goal: "Repair the service and commit the result." },
|
|
1718
|
+
{}
|
|
1719
|
+
),
|
|
1720
|
+
"broad find deletion was incorrectly classified as optional Python cache cleanup"
|
|
1721
|
+
);
|
|
1683
1722
|
assert(
|
|
1684
1723
|
!isUnrequestedCleanupCommand(
|
|
1685
1724
|
"run_command",
|
|
@@ -13546,6 +13546,91 @@ Do not prefix, suffix, wrap, redirect, pipe, or combine that validator command.`
|
|
|
13546
13546
|
"a partial multi-artifact task entered task-owned Git completion before its deliverables existed"
|
|
13547
13547
|
);
|
|
13548
13548
|
|
|
13549
|
+
const securityWorkspace = path.join(tempRoot, "security-test-before-commit");
|
|
13550
|
+
await fs.mkdir(securityWorkspace, { recursive: true });
|
|
13551
|
+
await fs.writeFile(path.join(securityWorkspace, "labshare.py"), "def record_access():\n pass\n");
|
|
13552
|
+
const securityGoal = [
|
|
13553
|
+
"Independent acceptance failed: audit fields permit newline log injection.",
|
|
13554
|
+
"Add a focused regression test, fix the root cause, run the full visible tests,",
|
|
13555
|
+
"review the diff, and commit only this corrective work.",
|
|
13556
|
+
].join(" ");
|
|
13557
|
+
const securityState = {
|
|
13558
|
+
goal: securityGoal,
|
|
13559
|
+
commandCwd: securityWorkspace,
|
|
13560
|
+
plan: "Patch the source and regression test, run the full suite, then commit the verified work.",
|
|
13561
|
+
messages: [
|
|
13562
|
+
{
|
|
13563
|
+
role: "tool",
|
|
13564
|
+
content: JSON.stringify({
|
|
13565
|
+
ok: true,
|
|
13566
|
+
toolName: "apply_patch",
|
|
13567
|
+
path: "labshare.py",
|
|
13568
|
+
goalRevision: 1,
|
|
13569
|
+
projectMutationRevision: 1,
|
|
13570
|
+
}),
|
|
13571
|
+
},
|
|
13572
|
+
{
|
|
13573
|
+
role: "tool",
|
|
13574
|
+
content: JSON.stringify({
|
|
13575
|
+
ok: true,
|
|
13576
|
+
toolName: "run_command",
|
|
13577
|
+
exitCode: 0,
|
|
13578
|
+
args: { command: "git status --short" },
|
|
13579
|
+
stdout: " M labshare.py\n",
|
|
13580
|
+
goalRevision: 1,
|
|
13581
|
+
projectMutationRevision: 1,
|
|
13582
|
+
}),
|
|
13583
|
+
},
|
|
13584
|
+
],
|
|
13585
|
+
meta: {
|
|
13586
|
+
taskProfile: "security",
|
|
13587
|
+
goalContract: {
|
|
13588
|
+
revision: 1,
|
|
13589
|
+
currentRequest: securityGoal,
|
|
13590
|
+
taskGoal: securityGoal,
|
|
13591
|
+
activeGoal: securityGoal,
|
|
13592
|
+
activeGoalRevision: 1,
|
|
13593
|
+
history: [{ revision: 1, refreshExecutionContract: true }],
|
|
13594
|
+
lifecycle: [{ at: new Date(Date.now() - 2000).toISOString() }],
|
|
13595
|
+
},
|
|
13596
|
+
// Reproduce the pre-fix persisted contract that recognized a mutation
|
|
13597
|
+
// but failed to classify the requested regression test as file work.
|
|
13598
|
+
activeExecutionContract: {
|
|
13599
|
+
revision: 1,
|
|
13600
|
+
startedMutationRevision: 0,
|
|
13601
|
+
materialMutationRevision: 1,
|
|
13602
|
+
requiresWorkspaceMutation: true,
|
|
13603
|
+
requiresFileMutation: false,
|
|
13604
|
+
},
|
|
13605
|
+
projectVerification: {
|
|
13606
|
+
mutationRevision: 1,
|
|
13607
|
+
mutationHistory: [
|
|
13608
|
+
{
|
|
13609
|
+
revision: 1,
|
|
13610
|
+
at: new Date().toISOString(),
|
|
13611
|
+
toolName: "apply_patch",
|
|
13612
|
+
paths: ["labshare.py"],
|
|
13613
|
+
goalRevision: 1,
|
|
13614
|
+
},
|
|
13615
|
+
],
|
|
13616
|
+
},
|
|
13617
|
+
},
|
|
13618
|
+
};
|
|
13619
|
+
const securityCompletionContract = completionTaskContract(
|
|
13620
|
+
{ goal: securityGoal, taskProfile: "security", commandCwd: securityWorkspace },
|
|
13621
|
+
securityState
|
|
13622
|
+
);
|
|
13623
|
+
const preTestSecurityRuntime = nextStepRuntimeConfig(
|
|
13624
|
+
{ goal: securityGoal, taskProfile: "security", commandCwd: securityWorkspace },
|
|
13625
|
+
securityState
|
|
13626
|
+
);
|
|
13627
|
+
assert(
|
|
13628
|
+
securityCompletionContract.requiresFileMutation === true &&
|
|
13629
|
+
securityCompletionContract.requiredEvidence.some((item) => item.category === "test") &&
|
|
13630
|
+
preTestSecurityRuntime.taskOwnedCommitPending !== true,
|
|
13631
|
+
"a regression-test repair entered task-owned Git completion before fresh test evidence"
|
|
13632
|
+
);
|
|
13633
|
+
|
|
13549
13634
|
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
13550
13635
|
console.log("smoke-dynamic-step-budget ok");
|
|
13551
13636
|
} catch (error) {
|
|
@@ -930,6 +930,22 @@ const refreshedSecurityContract = deriveScsTaskContract({
|
|
|
930
930
|
goal: "Fix the security issue and run the regression tests.",
|
|
931
931
|
taskProfile: "security",
|
|
932
932
|
});
|
|
933
|
+
const correctiveSecurityContract = deriveScsTaskContract({
|
|
934
|
+
goal: [
|
|
935
|
+
"Independent acceptance failed: audit fields permit newline log injection.",
|
|
936
|
+
"Reopen the exact repair, add a focused regression test, fix the root cause,",
|
|
937
|
+
"run the full visible tests, review the diff, and commit only this corrective work.",
|
|
938
|
+
].join(" "),
|
|
939
|
+
taskProfile: "security",
|
|
940
|
+
});
|
|
941
|
+
assert(
|
|
942
|
+
correctiveSecurityContract.requiresWorkspaceMutation === true &&
|
|
943
|
+
correctiveSecurityContract.requiresFileMutation === true &&
|
|
944
|
+
correctiveSecurityContract.requiredEvidence.some((item) => item.category === "file") &&
|
|
945
|
+
correctiveSecurityContract.requiredEvidence.some((item) => item.category === "test") &&
|
|
946
|
+
correctiveSecurityContract.requiredGitActions.includes("commit"),
|
|
947
|
+
"a requested regression-test repair could enter Git completion without fresh file and test evidence"
|
|
948
|
+
);
|
|
933
949
|
const staleContractFinish = await reviewScsFinish(
|
|
934
950
|
{ mock: true },
|
|
935
951
|
{ provider: "mock", model: "mock-agent", taskProfile: "security" },
|
|
@@ -964,6 +980,11 @@ const staleContractFinish = await reviewScsFinish(
|
|
|
964
980
|
exitCode: 0,
|
|
965
981
|
args: { command: "python -m unittest discover -s tests -v" },
|
|
966
982
|
stdout: "Ran 10 tests\nOK",
|
|
983
|
+
projectTest: {
|
|
984
|
+
passed: true,
|
|
985
|
+
command: "python -m unittest discover -s tests -v",
|
|
986
|
+
mutationRevision: 0,
|
|
987
|
+
},
|
|
967
988
|
}),
|
|
968
989
|
},
|
|
969
990
|
],
|
|
@@ -1056,6 +1077,20 @@ assert(
|
|
|
1056
1077
|
evaluateScsEvidence(commitContract, gitStatusLedger).missingGitActions.includes("commit"),
|
|
1057
1078
|
"the completion deficit did not identify the missing commit"
|
|
1058
1079
|
);
|
|
1080
|
+
const readOnlyGitContract = {
|
|
1081
|
+
requiresExternalEvidence: true,
|
|
1082
|
+
requiredEvidence: [
|
|
1083
|
+
{ id: "command", category: "command", description: "read-only verification" },
|
|
1084
|
+
{ id: "git", category: "git", description: "existing commit verification" },
|
|
1085
|
+
],
|
|
1086
|
+
requiredToolCalls: [],
|
|
1087
|
+
requiredGitActions: [],
|
|
1088
|
+
requiredProjectCommands: [],
|
|
1089
|
+
};
|
|
1090
|
+
assert(
|
|
1091
|
+
evaluateScsEvidence(readOnlyGitContract, gitStatusLedger).ok,
|
|
1092
|
+
"read-only Git evidence was rejected when the contract required no consequential Git action"
|
|
1093
|
+
);
|
|
1059
1094
|
const committedEvaluation = evaluateScsEvidence(commitContract, gitCommitLedger);
|
|
1060
1095
|
assert(
|
|
1061
1096
|
committedEvaluation.missingGitActions.length === 0 &&
|
|
@@ -1231,7 +1266,14 @@ const checkedCodeEval = evaluateScsEvidence(
|
|
|
1231
1266
|
messages: [
|
|
1232
1267
|
{
|
|
1233
1268
|
role: "tool",
|
|
1234
|
-
content: JSON.stringify({
|
|
1269
|
+
content: JSON.stringify({
|
|
1270
|
+
toolName: "run_command",
|
|
1271
|
+
ok: true,
|
|
1272
|
+
exitCode: 0,
|
|
1273
|
+
args: { command: "npm test" },
|
|
1274
|
+
stdout: "ok",
|
|
1275
|
+
projectTest: { passed: true, command: "npm test", mutationRevision: 0 },
|
|
1276
|
+
}),
|
|
1235
1277
|
},
|
|
1236
1278
|
],
|
|
1237
1279
|
},
|
package/scripts/smoke-skills.js
CHANGED
|
@@ -132,6 +132,12 @@ for (const unrelated of [
|
|
|
132
132
|
}
|
|
133
133
|
assert(selectedIds("debug Docker deployment logs and port config").includes("devops-deployment"), "devops prompt did not select devops-deployment");
|
|
134
134
|
assert(selectedIds("review auth security and secrets handling").includes("security-review"), "security prompt did not select security-review");
|
|
135
|
+
const securitySkill = skills.find((skill) => skill.id === "security-review");
|
|
136
|
+
assert(
|
|
137
|
+
securitySkill?.body.includes("carriage-return, newline") &&
|
|
138
|
+
securitySkill?.body.includes("residual risks and non-goals"),
|
|
139
|
+
"security skill omitted audit-record injection or structured security-note guidance"
|
|
140
|
+
);
|
|
135
141
|
assert(selectedIds("make a PowerPoint pitch deck").includes("presentation-slides"), "slides prompt did not select presentation-slides");
|
|
136
142
|
const presentationSkill = skills.find((skill) => skill.id === "presentation-slides");
|
|
137
143
|
assert(presentationSkill?.body.includes("Render every slide"), "presentation skill does not require every slide to be rendered");
|
|
@@ -71,6 +71,26 @@ const patchResult = await executeWorkspaceTool(
|
|
|
71
71
|
);
|
|
72
72
|
assert(patchResult.ok, "legacy apply_patch replace content with safe credential-loading code context should not be blocked");
|
|
73
73
|
|
|
74
|
+
const maskedAuditPatchResult = await executeWorkspaceTool(
|
|
75
|
+
"apply_patch",
|
|
76
|
+
{
|
|
77
|
+
patch: [
|
|
78
|
+
"*** Begin Patch",
|
|
79
|
+
"*** Add File: tests/test_audit_mask.py",
|
|
80
|
+
"+def test_audit_token_is_masked():",
|
|
81
|
+
"+ assert 'token=***' == 'token=' + ('*' * 3)",
|
|
82
|
+
"*** End Patch",
|
|
83
|
+
].join("\n"),
|
|
84
|
+
},
|
|
85
|
+
config
|
|
86
|
+
);
|
|
87
|
+
assert(maskedAuditPatchResult.ok, "an all-asterisk credential mask in regression code must not be treated as a secret");
|
|
88
|
+
assert.equal(
|
|
89
|
+
redactSensitiveText("token=***"),
|
|
90
|
+
"token=***",
|
|
91
|
+
"all-asterisk credential masks must remain visible as non-secret evidence"
|
|
92
|
+
);
|
|
93
|
+
|
|
74
94
|
const secretResult = await executeWorkspaceTool(
|
|
75
95
|
"write_file",
|
|
76
96
|
{ path: "notes/leak-report.txt", content: "DEMO_SECRET_TOKEN=aginti_fake_do_not_use\n", mode: "create" },
|
|
@@ -30,7 +30,9 @@ Inspect auth boundaries, file/path handling, shell command construction, depende
|
|
|
30
30
|
|
|
31
31
|
- Do not label a finding HIGH or CRITICAL from pattern matching alone. A high-severity claim needs either a reproduced exploit, a direct code path proof, or a clearly stated "potential/unverified" label with lower severity.
|
|
32
32
|
- For path traversal, SSRF, command injection, open redirect, auth bypass, and file disclosure, test a minimal negative case when a safe local command is available. If runtime policy blocks the test, report the blocker and keep severity conservative.
|
|
33
|
+
- Treat logs, audit records, CSV rows, headers, and other line- or delimiter-based outputs as injection boundaries. Check every untrusted field for carriage-return, newline, delimiter, and control-character injection, and add a regression proving one input event cannot forge extra records.
|
|
33
34
|
- Exclude generated/vendor/session/cache directories such as `node_modules/`, `.sessions/`, `.aginti-sessions/`, `.aginti-thorough-tests/`, build outputs, and coverage when broad scanning. If a scan accidentally includes them, call that out as noisy evidence and do not base severity solely on those hits.
|
|
34
35
|
- When a policy blocks `.env`, key, credential, or permission checks, treat that as a safety limitation. Do not retry variants to bypass the block and do not imply the file content was inspected.
|
|
35
36
|
- Prefer bounded commands: `rg`/`grep` with explicit `--exclude-dir`, `head`, `timeout`, and project-relative paths. Avoid unbounded recursive scans over the whole workspace.
|
|
36
37
|
- Reports should separate confirmed findings, potential findings, false positives, and limitations. Include verification commands and observed results for every high-severity item.
|
|
38
|
+
- A standalone security note should distinguish the deployment boundary, concrete threat model, implemented controls, residual risks and non-goals, and exact verification procedure. Ground each claim in current source or tests; do not substitute a generic security checklist.
|
package/src/permission-advice.js
CHANGED
|
@@ -175,7 +175,14 @@ export function isUnrequestedCleanupCommand(toolName = "", args = {}, config = {
|
|
|
175
175
|
return command
|
|
176
176
|
.split(/(?:&&|;|\n)/)
|
|
177
177
|
.map((segment) => segment.trim())
|
|
178
|
-
.some(
|
|
178
|
+
.some(
|
|
179
|
+
(segment) =>
|
|
180
|
+
/^(?:command\s+)?rm\s+(?:-[A-Za-z]*[fr][A-Za-z]*\s+|--force\s+)/.test(segment) ||
|
|
181
|
+
/^find\s+\.\s+-type\s+d\s+-name\s+['"]?__pycache__['"]?\s+-prune\s+-exec\s+rm\s+-rf\s+\{\}\s+\+$/.test(
|
|
182
|
+
segment
|
|
183
|
+
) ||
|
|
184
|
+
/^find\s+\.\s+-type\s+f\s+-name\s+(['"]?)\*\.pyc\1\s+-delete$/.test(segment)
|
|
185
|
+
);
|
|
179
186
|
}
|
|
180
187
|
|
|
181
188
|
function quoteShell(value = "") {
|
package/src/redaction.js
CHANGED
|
@@ -8,7 +8,7 @@ const SECRET_PATTERNS = [
|
|
|
8
8
|
];
|
|
9
9
|
|
|
10
10
|
const SECRET_ASSIGNMENT_PATTERN = /(\b(?:[A-Za-z0-9]+[_-])*(?:api[_-]?key|apiKey|auth[_-]?token|authToken|token|secret|password|passwd|npm[_-]?token|npmToken|_authToken|grsai|venice[_-]?api[_-]?key|veniceApiKey|openrouter[_-]?api[_-]?key)[^\S\r\n]*([:=])[^\S\r\n]*)([^\s\\"'`,;|(){}]+)/gi;
|
|
11
|
-
const SAFE_ASSIGNMENT_VALUE_PATTERN = /^(?:false|true|null|none|unset|missing|not[-_ ]?set|\[REDACTED\])$/i;
|
|
11
|
+
const SAFE_ASSIGNMENT_VALUE_PATTERN = /^(?:false|true|null|none|unset|missing|not[-_ ]?set|\[REDACTED\]|\*{3,})$/i;
|
|
12
12
|
const SOURCE_TYPE_ANNOTATION_PATTERN = /^(?:str|string|bytes|bytearray|int|float|complex|bool|dict|list|tuple|set|frozenset|object|any|unknown|never|void|path|(?:typing\.)?(?:Any|Optional|Union|Literal|Annotated|Sequence|Mapping|MutableMapping|Callable|Type|ClassVar|Final|List|Dict|Tuple|Set|FrozenSet)\[[^\r\n]{1,120}\])$/i;
|
|
13
13
|
const SOURCE_CALL_HEAD_PATTERN = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
|
|
14
14
|
|
package/src/scs-evidence.js
CHANGED
|
@@ -266,7 +266,11 @@ export function gitActionsSatisfyContract(contract = {}, actions = []) {
|
|
|
266
266
|
? contract.requiredGitActions.map((item) => String(item || "").toLowerCase()).filter(Boolean)
|
|
267
267
|
: [];
|
|
268
268
|
if (required.length) return missingRequiredGitActionSequence(required, observed).length === 0;
|
|
269
|
-
|
|
269
|
+
// A Git evidence requirement can be purely observational, for example when
|
|
270
|
+
// a continuation asks to verify an existing commit while explicitly
|
|
271
|
+
// forbidding another commit. Consequential actions remain governed by the
|
|
272
|
+
// ordered requiredGitActions contract above.
|
|
273
|
+
return observed.length > 0;
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
const PROJECT_TEST_PROFILES = new Set([
|
|
@@ -1233,14 +1237,37 @@ function codeProfileRequiresCommand(goal = "") {
|
|
|
1233
1237
|
return substantiveCodeWork && !simpleDocumentWrite;
|
|
1234
1238
|
}
|
|
1235
1239
|
|
|
1240
|
+
function goalRequestsExplicitTestMutation(text = "") {
|
|
1241
|
+
return (
|
|
1242
|
+
/\b(?:add|create|implement|write)\b(?:\s+(?:a|an|the|focused|new|additional|specific|security|unit|integration|regression))*\s+(?:regression\s+)?(?:tests?|test cases?)\b/.test(
|
|
1243
|
+
text
|
|
1244
|
+
) ||
|
|
1245
|
+
/\b(?:edit|fix|modify|patch|repair|update)\b(?:\s+(?:a|an|the|focused|new|existing|current|failing|specific|security|unit|integration|regression))*\s+(?:regression\s+)?(?:tests?|test cases?)\b/.test(
|
|
1246
|
+
text
|
|
1247
|
+
) ||
|
|
1248
|
+
/(?:添加|新增|创建|编写|编辑|修复|修改|更新)(?:一个|新的|现有的|失败的|专门的|回归|单元|集成)*测试(?:用例)?/.test(
|
|
1249
|
+
text
|
|
1250
|
+
)
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1236
1254
|
function goalRequestsWorkspaceMutation(goal = "", taskProfile = "") {
|
|
1237
1255
|
const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
|
|
1238
|
-
|
|
1239
|
-
|
|
1256
|
+
const explicitAddMutation =
|
|
1257
|
+
goalRequestsExplicitTestMutation(text) ||
|
|
1258
|
+
/\badd\b(?:\s+(?:a|an|the|new|additional|specific))*\s+(?:code|documents?|files?|notes?|readme|scripts?|source|workspace)\b/.test(
|
|
1240
1259
|
text
|
|
1260
|
+
) || /(?:添加|新增)(?:一个|新的|额外的|特定的)*(?:文件|文档|代码|脚本|源码)/.test(text);
|
|
1261
|
+
if (String(taskProfile || "").trim().toLowerCase() === "review") {
|
|
1262
|
+
return (
|
|
1263
|
+
explicitAddMutation ||
|
|
1264
|
+
/\b(?:append|copy|create|delete|edit|fix|implement|modify|move|patch|refactor|remove|rename|repair|replace|rewrite|save|update|write)\b/.test(
|
|
1265
|
+
text
|
|
1266
|
+
)
|
|
1241
1267
|
);
|
|
1242
1268
|
}
|
|
1243
1269
|
return (
|
|
1270
|
+
explicitAddMutation ||
|
|
1244
1271
|
/\b(?:append|build|convert|copy|create|delete|edit|fix|generate|implement|modify|move|patch|refactor|remove|rename|repair|replace|rewrite|save|update|write)\b/.test(
|
|
1245
1272
|
text
|
|
1246
1273
|
) ||
|
|
@@ -1251,7 +1278,9 @@ function goalRequestsWorkspaceMutation(goal = "", taskProfile = "") {
|
|
|
1251
1278
|
function goalRequestsFileMutation(goal = "", taskProfile = "") {
|
|
1252
1279
|
const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
|
|
1253
1280
|
if (!goalRequestsWorkspaceMutation(text, taskProfile)) return false;
|
|
1281
|
+
const explicitTestMutation = goalRequestsExplicitTestMutation(text);
|
|
1254
1282
|
return (
|
|
1283
|
+
explicitTestMutation ||
|
|
1255
1284
|
/\b(?:code|codebase|document(?:ation)?|files?|notes?|path|readme|repo(?:sitory)?|script|source|workspace)\b/.test(
|
|
1256
1285
|
text
|
|
1257
1286
|
) ||
|
|
@@ -1263,6 +1292,17 @@ function goalRequestsFileMutation(goal = "", taskProfile = "") {
|
|
|
1263
1292
|
);
|
|
1264
1293
|
}
|
|
1265
1294
|
|
|
1295
|
+
function goalRequestsTestExecution(goal = "") {
|
|
1296
|
+
const text = normalizedText(stripCompletedWorkNarration(stripForbiddenLanguage(goal)));
|
|
1297
|
+
return (
|
|
1298
|
+
/\b(?:run|rerun|re-run|execute|invoke)\b[^.\n;]{0,140}\b(?:tests?|test suite)\b/.test(text) ||
|
|
1299
|
+
/\b(?:tests?|test suite)\b[^.\n;]{0,120}\b(?:pass|passing|green|run|rerun|re-run|execute)\b/.test(text) ||
|
|
1300
|
+
/(?:运行|执行|重跑|重新运行)[^。;\n]{0,100}(?:测试|测试套件)|(?:测试|测试套件)[^。;\n]{0,80}(?:通过|运行|执行)/.test(
|
|
1301
|
+
text
|
|
1302
|
+
)
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1266
1306
|
function profileRequirementsForGoal(taskProfile = "", goal = "") {
|
|
1267
1307
|
const profile = String(taskProfile || "").toLowerCase();
|
|
1268
1308
|
const defaults = PROFILE_REQUIREMENTS[profile] || [];
|
|
@@ -1367,6 +1407,9 @@ function inferRequirementCategories(goal = "", taskProfile = "", acceptanceCrite
|
|
|
1367
1407
|
if (directCommandSignal || (validationSignal && codeProfileRequiresCommand(positiveGoal))) {
|
|
1368
1408
|
categories.add("command");
|
|
1369
1409
|
}
|
|
1410
|
+
if (goalRequestsTestExecution(positiveGoal)) {
|
|
1411
|
+
categories.add("test");
|
|
1412
|
+
}
|
|
1370
1413
|
if (textHas(mandatoryEvidenceText, /\b(artifact|canvas|pdf|image|video|screenshot|cover|plot|chart|figure|docx|archive|copy to|export|generated|generate|draft)\b/) || /输出|产物|图片|视频|截图|封面|生成/.test(mandatoryEvidenceText)) {
|
|
1371
1414
|
categories.add("artifact");
|
|
1372
1415
|
}
|
package/src/workspace-tools.js
CHANGED
|
@@ -367,7 +367,7 @@ function stripSafeCredentialReferences(content) {
|
|
|
367
367
|
const codeKeyName = String.raw`(?:api[_-]?key|apiKey|token|secret|password|passwd|npmToken|authToken|grsai|veniceApiKey|venice_api_key)`;
|
|
368
368
|
const codeExpression = String.raw`[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\([^"'\n]*\))?`;
|
|
369
369
|
const envName = String.raw`[A-Z][A-Z0-9_]*(?:API[_-]?KEY|TOKEN|SECRET|PASSWORD|PASSWD|GRSAI|VENICE)[A-Z0-9_]*`;
|
|
370
|
-
const safeStatus = String.raw`(?:false|true|null|none|unset|missing|not[-_ ]?set|\[REDACTED\])`;
|
|
370
|
+
const safeStatus = String.raw`(?:false|true|null|none|unset|missing|not[-_ ]?set|\[REDACTED\]|\*{3,})`;
|
|
371
371
|
|
|
372
372
|
text = text.replace(
|
|
373
373
|
new RegExp(String.raw`\b${keyName}\s*[:=]\s*${safeStatus}\b`, "gi"),
|