diff --git a/.agent b/.agent index 987c74f2..06be4132 160000 --- a/.agent +++ b/.agent @@ -1 +1 @@ -Subproject commit 987c74f24598af35d6d77dd4d50073732cb8cd6a +Subproject commit 06be413220dcf4e14f5a951d129536534688c3ec diff --git a/.github/workflows/offline-package-acceptance.yml b/.github/workflows/offline-package-acceptance.yml index a5f80e7b..eb52b5d4 100644 --- a/.github/workflows/offline-package-acceptance.yml +++ b/.github/workflows/offline-package-acceptance.yml @@ -28,7 +28,7 @@ jobs: node: ["22", "24"] env: SOURCE_HEAD: ${{ github.event.pull_request.head.sha || github.sha }} - EXPECTED_GITLINK: 987c74f24598af35d6d77dd4d50073732cb8cd6a + EXPECTED_GITLINK: 06be413220dcf4e14f5a951d129536534688c3ec steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 with: diff --git a/.github/workflows/registry-upgrade-acceptance.yml b/.github/workflows/registry-upgrade-acceptance.yml index bec77a2b..bba52cf7 100644 --- a/.github/workflows/registry-upgrade-acceptance.yml +++ b/.github/workflows/registry-upgrade-acceptance.yml @@ -36,7 +36,7 @@ jobs: env: BASELINE_VERSION: ${{ matrix.baseline }} SOURCE_HEAD: ${{ github.event.pull_request.head.sha || github.sha }} - EXPECTED_GITLINK: 987c74f24598af35d6d77dd4d50073732cb8cd6a + EXPECTED_GITLINK: 06be413220dcf4e14f5a951d129536534688c3ec EXPECTED_CANDIDATE_VERSION: "1.15.7" DISPATCH_HEAD: ${{ inputs.expected_head }} DISPATCH_VERSION: ${{ inputs.expected_version }} diff --git a/scripts/check-runtime-artifact-location.js b/scripts/check-runtime-artifact-location.js index e1cfcabb..6f9821dd 100644 --- a/scripts/check-runtime-artifact-location.js +++ b/scripts/check-runtime-artifact-location.js @@ -3,10 +3,9 @@ // ─── Check Runtime Artifact Location ────────────────────────────────────────── // Mechanical gate for the L1 rule `runtime-artifacts-location.md`: -// in a cortex-agent-managed project, every runtime artifact (logs, probe -// scripts, per-run output, observations, scratch JSON) belongs under the -// project `.agent/` directory — never in /tmp, the home directory, or the -// project root. +// classify scripts, logs, and outputs by lifetime. Cross-command material +// belongs under the project `.agent/` directory; process plumbing may use the +// OS temp directory only when it is removed inside the same top-level command. // // The rule is written as prose, so prose alone does not keep a skill from // shipping a `os.tmpdir()` draft directory to every managed project. This @@ -98,6 +97,20 @@ const ARTIFACT_EXTENSIONS = Object.freeze([ "md", "json", "jsonl", "ndjson", "log", "txt", "csv", "yaml", "yml", ]); +// A host patch creates a file in one tool call and normally consumes it in a +// later call. That is already cross-command material, so every temp target is +// a violation even when the file is not named like a report or log. +const APPLY_PATCH_TARGET = /\*\*\*\s+(?:Add|Update|Delete) File:\s*([^\r\n]+)/gi; + +// Shell/PowerShell writes do not go through Node's fs API. Detect the common +// operators explicitly; the destination, rather than any mention of a temp +// path, determines whether the line writes into a temp directory. +const SHELL_REDIRECT = /(?-])>{1,2}\s*(?:"([^"]+)"|'([^']+)'|([^\s;|&]+))/g; +const SHELL_TEMP_WRITER = /\b(?:tee|Out-File|Set-Content|Add-Content)\b[^\r\n]*/i; +const SHELL_OUTPUT_FLAG = /(?:--output(?:=|\s+)|(?:^|\s)-o\s+)(?:"([^"]+)"|'([^']+)'|([^\s;|&]+))/gi; +const SHELL_COPY = /\b(?:cp|mv|install|Copy-Item|Move-Item)\b([^\r\n]*)/i; +const HAS_SHELL_CLEANUP = /\btrap\s+[^\n]*(?:\brm\b|Remove-Item)|\brm\s+-[A-Za-z]*r[^\n]*\b(?:\$TMPDIR|\/tmp|\/private\/tmp)\/|\bRemove-Item\b[^\n]*(?:-Recurse|-Force)/i; + // Splits a file name into its stem tokens, so that "catalog.json" does not // read as an artifact just because "log" appears inside it, while // "session-signals.json" does. @@ -145,6 +158,9 @@ const ALLOW_PATTERNS = Object.freeze([ /GIT_INDEX_FILE/, // A fixture that is removed again in the same breath. /rmSync|rimraf|removeSync|unlinkSync/, + // Promoting a temp file into .agent/ is the required repair, not a write + // back into temp. + /\bmv\b[^\n]*\/(?:tmp|private\/tmp|var\/folders)\/[^\n]*\.agent\//i, ]); // A test file is where a fixture legitimately lives in a temp directory: the @@ -176,6 +192,72 @@ function containsArtifactName(text) { return namesArtifactFile(text) || ARTIFACT_DIRNAME.test(text); } +function referencesTemp(text, tempVars) { + return containsTempSource(text) || + [...tempVars].some((name) => new RegExp(`\\b${name}\\b`).test(text)); +} + +// Split the small command-line subset relevant to copy/move destinations. +// This is deliberately not a shell parser; it only keeps quoted paths intact +// so a destination containing spaces is still classified correctly. +function shellArguments(text) { + const args = []; + let current = ""; + let quote = null; + for (let i = 0; i < text.length; i += 1) { + const ch = text[i]; + if (quote) { + if (ch === quote) quote = null; + else current += ch; + continue; + } + if (ch === '"' || ch === "'") { + quote = ch; + continue; + } + if (/\s/.test(ch)) { + if (current) args.push(current); + current = ""; + continue; + } + current += ch; + } + if (current) args.push(current); + return args; +} + +// Return concrete destinations that are written by shell syntax on this line. +// A `mv /tmp/file .agent/...` source alone is not a write back into temp. +function shellTempDestinations(line, tempVars) { + // Markdown blockquotes begin with `>` and are documentation, not shell + // redirection. A shell command that writes without any command part is not + // useful in the repository patterns this gate needs to protect. + if (/^\s*>\s/.test(line)) return []; + const destinations = []; + let match; + + for (const pattern of [SHELL_REDIRECT, SHELL_OUTPUT_FLAG]) { + pattern.lastIndex = 0; + while ((match = pattern.exec(line)) !== null) { + const target = match[1] || match[2] || match[3]; + if (target && referencesTemp(target, tempVars)) destinations.push(target); + } + } + + if (SHELL_TEMP_WRITER.test(line) && referencesTemp(line, tempVars)) { + destinations.push(line.trim().slice(0, 120)); + } + + const copy = line.match(SHELL_COPY); + if (copy) { + const args = shellArguments(copy[1]).filter((arg) => !arg.startsWith("-")); + const destination = args[args.length - 1]; + if (destination && referencesTemp(destination, tempVars)) destinations.push(destination); + } + + return destinations; +} + // ─── Taint analysis ─────────────────────────────────────────────────────────── // Extracts the first argument of a call, respecting nesting so that @@ -250,11 +332,41 @@ function analyseFile(filePath, root) { // in another is still caught. const tempVars = new Set(); const artifactVars = new Set(); + const hasShellCleanup = HAS_SHELL_CLEANUP.test(text); const lines = text.split("\n"); lines.forEach((line, idx) => { if (isAllowlisted(line)) return; + // apply_patch is a host edit: the file survives the tool call by design, + // so even a one-off script target crosses the process-local boundary. + APPLY_PATCH_TARGET.lastIndex = 0; + let patchTarget; + while ((patchTarget = APPLY_PATCH_TARGET.exec(line)) !== null) { + if (containsTempSource(patchTarget[1])) { + findings.push({ + rule: "apply-patch-target-in-temp", + title: "host patch targets a temp path", + file: rel, + line: idx + 1, + detail: patchTarget[0].trim().slice(0, 120), + }); + } + } + + if (!hasShellCleanup) { + const destinations = shellTempDestinations(line, tempVars); + if (destinations.length > 0) { + findings.push({ + rule: "shell-write-to-temp", + title: "shell syntax writes to a temp path", + file: rel, + line: idx + 1, + detail: destinations[0].slice(0, 120), + }); + } + } + const assignment = line.match(ASSIGNMENT); if (assignment) { const [, name, expr] = assignment; diff --git a/templates/_shared/.agent/rules/runtime-artifacts-location.md b/templates/_shared/.agent/rules/runtime-artifacts-location.md index 0cf40b37..b434efc7 100644 --- a/templates/_shared/.agent/rules/runtime-artifacts-location.md +++ b/templates/_shared/.agent/rules/runtime-artifacts-location.md @@ -1,6 +1,6 @@ --- title: "Runtime Artifact Location" -description: '> All runtime artifacts produced by a cortex-agent-managed project — logs, probe scripts, per-run output, observations, scratch JSON — must live under the project .agent/ directory, never in /tmp, the home directory, or the project root.' +description: 'Classify every script, log, and output by lifetime. Cross-command, review, or handoff artifacts belong under .agent/; process-local scratch may use the OS temp directory only when it is deleted and verified within the same top-level command.' type: rule scope: L1 applicable_to: @@ -8,17 +8,45 @@ applicable_to: linked_workflows: [] linked_skills: [] owner: Kucell -last_verified: 2026-10-28 +last_verified: 2026-11-07 status: stable linked_decisions: ["D-OKF-001"] --- # Runtime Artifact Location -> **Core rule**: in a project managed by cortex-agent, everything the agent produces while -> running, debugging, or investigating — logs, probe scripts, per-run output, -> observations, scratch JSON, screenshots — belongs under the project .agent/ -> directory. It does **not** belong in /tmp, the home directory, or the project root. +> **Core rule**: choose a script, log, or output's location by its lifetime. If it +> must survive the producing process, or will be read by another command, reviewer, +> handoff, or task, it belongs under the project's `.agent/` directory. The OS temp +> directory is reserved for process plumbing that is deleted and verified within the +> same top-level command. +> +> **Cross-command means durable**: creating a script with `apply_patch`, recording its +> path for a later command, or letting another agent read its output crosses that +> boundary. Such files must not live in `/tmp`, `os.tmpdir()`, `%TEMP%`, or `$TMPDIR`. + +Three questions determine the answer; the filename alone does not: + +1. Must it survive after the current process or top-level command ends? +2. Will another command, reviewer, agent, or resumed task read it? +3. Is it a reusable probe, measurement, or delivery asset? + +If any answer is yes, use the `.agent/` lifecycle table below. + +--- + +## Lifetime and destination + +| Lifetime / use | Location | Closeout requirement | +|---|---|---| +| Valid only while one process or top-level command runs | OS temp directory | Delete and verify absence in the same command | +| Needed across commands or for short review, but not durable evidence | `.agent/debug/temp/-/` | Keep a file inventory and retention condition | +| Reusable across tasks | `.agent/skills//scripts/` | Provide an entry point and tests | +| Mission-specific tool or long-lived evidence | `.agent/missions//scripts/` or `evidence/` | Bind to source identity and verification result | +| Machine-readable summary for another agent | `.agent/artifacts/` | Keep compact references, hashes, and payloads | + +Do not evade the rule by renaming a directory to `temp`, `scratch`, or `debug`. +Lifetime and consumer determine placement. --- @@ -107,19 +135,51 @@ Use .: - ❌ **Do not treat .agent/debug/ as release evidence.** Long-lived evidence belongs in .agent/missions//evidence/ or the project documentation assets. -### The one legitimate exception +### Cross-command boundary + +A top-level command is one indivisible host invocation, including synchronous child +processes in its process tree. The boundary has been crossed when: + +- `apply_patch`, `Write`, or `Edit` creates a file and a later tool command uses it; +- command A creates a script or output and command B reads, uploads, forwards, or + updates it by absolute path; +- shell redirection, `tee`, `cp`, `mv`, or a program argument writes to a temp path + but cleanup happens in another command; +- a temp path is persisted in task progress, a handoff, README, configuration, or + another durable document. + +Cross-command material must be created under `.agent/` directly. For a probe or output +needed briefly for review, use `.agent/debug/temp/-/` and record each +file's purpose and retention condition in `inventory.json`. -Short-lived **process plumbing** may use the OS temp directory, provided it is -genuinely ephemeral and holds nothing that must survive the process: +### Single-command process plumbing -- a private context file passed to a child process and deleted right after; -- a scratch build directory for a throwaway compile; -- a test fixture directory created and removed inside a single test case. +Process plumbing may use the OS temp directory only when all of these hold: -The test is simple: **if the artifact has value after the process exits, it -belongs under .agent/.** A secret-bearing context file also belongs nowhere -persistent — it must be mode 0600 and removed on completion, which is why it is -not a runtime artifact but process plumbing. +- its lifetime is bounded by a provable cleanup path in the same top-level command; +- `trap`, `finally`, or an equivalent mechanism cleans up on success, failure, and + interruption; +- absence is verified before the command returns; +- the path is not written into durable documentation, configuration, or a variable + table consumed by another command. + +Examples are a child-process context file (mode `0600`), a throwaway compile directory, +and a fixture created and removed inside one test case. A PowerShell, Python, Node, or +shell probe reused across commands is not process plumbing. + +### Task closeout + +Before a task is complete, inventory every temp path created by the run and classify it +as one of: + +- `deleted`: removed and verified absent; +- `promoted`: moved under `.agent/debug/temp/` or a longer-lived destination and all + references updated; +- `owned_by_live_process`: still used by a named running process, with the process and + retention condition recorded. + +Any unclassified path makes the closeout `TEMP_ARTIFACT_CLOSEOUT_INCOMPLETE`. "Will clean +later" is not a completed state. --- @@ -139,6 +199,22 @@ leaves the artifact lost. --- +## Mechanical check + +`pnpm check:runtime-artifact-location` scans the L1 templates, skills, workflows, and +framework code for writes that may place artifacts in a temp directory. It recognizes +the Node write APIs, `apply_patch` `Add/Update File` targets, shell redirection, +`tee`, `cp`, `mv`, and common `--output` arguments. A form whose lifecycle cannot be +proved fails closed; the author must choose an explicit `.agent/` destination or add a +documented exception. + +When a host exposes a Stop event, use it to inventory the run's temp paths and record +`deleted`, `promoted`, or `owned_by_live_process` for each one. If the host has no +blocking Stop gate, the same closeout remains a task-completion requirement and the +result must be reported explicitly. + +--- + ## Cross-references - .agent/rules/ai-debug-artifacts.md — the dedicated .agent/debug/ space for diff --git a/templates/en/.agent/rules/core-principles.md b/templates/en/.agent/rules/core-principles.md index 91658ce3..e342736e 100644 --- a/templates/en/.agent/rules/core-principles.md +++ b/templates/en/.agent/rules/core-principles.md @@ -17,7 +17,7 @@ status: stable 1. **Adhere to Language Settings**: Always communicate in the language defined in `.agent/rules/language.md`. 2. **Architecture First**: Always check `.agent/rules/architecture-design.md` before suggesting any code changes. 3. **Behavior Rules**: Read `.agent/rules/ai-behavior.md` at session start — it governs Git discipline, edit scope, code exploration strategy (including Graphify-first when `graphify-out/graph.json` exists), and staged-commit protocol. -4. **Runtime Artifact Location**: Anything that must persist across the session — per-run JSON, per-sample observations, debug logs, scratch JSON, probe scripts — goes under `.agent/`, per `.agent/rules/runtime-artifacts-location.md`. **Never** write to `/tmp`, the home directory, or the project root: those are reclaimed with the session and the artifact is lost. +4. **Runtime Artifact Location**: Choose the location by lifetime. JSON, logs, screenshots, and scripts that another command, reviewer, handoff, or resumed task will read belong under `.agent/`. Only process-local scratch created and verified deleted within the same top-level command may use the OS temp directory. See `.agent/rules/runtime-artifacts-location.md`. 5. **Memory Index**: At session start the SessionStart hook auto-loads `.agent/memory/MEMORY.md` (≤200 lines / 25KB cap); read topic files (`{user,feedback,project,reference}/*.md`) on demand. See `.agent/rules/memory-protocol.md` for details. 6. **Proposal Structure**: Read `.agent/rules/proposal-structure.md` before creating or reorganizing architecture proposals, especially for large projects or related-project proposal groups. 7. **Follow Workflows**: When a user runs a command (e.g., `/start-task`), strictly follow the corresponding workflow defined in `.agent/workflows/`. diff --git a/templates/en/.agent/rules/runtime-artifacts-location.md b/templates/en/.agent/rules/runtime-artifacts-location.md deleted file mode 100644 index e834e54e..00000000 --- a/templates/en/.agent/rules/runtime-artifacts-location.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: "Runtime Artifact Location" -description: '> All runtime artifacts produced by a cortex-agent-managed project — logs, probe scripts, per-run output, observations, scratch JSON — must live under the project .agent/ directory, never in /tmp, the home directory, or the project root.' -type: rule -scope: L1 -applicable_to: - - all -linked_workflows: [] -linked_skills: [] -owner: Kucell -last_verified: 2026-10-28 -status: stable -linked_decisions: ["D-OKF-001"] ---- - -# Runtime Artifact Location - -> **Core rule**: in a project managed by cortex-agent, everything the agent produces while -> running, debugging, or investigating — logs, probe scripts, per-run output, -> observations, scratch JSON, screenshots — belongs under the project `.agent/` -> directory. It does **not** belong in `/tmp`, the home directory, or the project root. - ---- - -## Why .agent/ and not /tmp - -`/tmp` is a scratch space with a short life: the OS reaps it, a session teardown -clears it, and a container restart wipes it. An artifact that a later session, a -teammate, or an independent reviewer needs to read must therefore not live there. - -`.agent/` is the project persistent, project-scoped state directory. An artifact -written there: - -1. **Survives session and host changes** — switching from Codex to Claude to Qoder - does not lose it, because it travels with the project. -2. **Is discoverable** — the location is predictable, so the next session knows - where to look without being told. -3. **Is reviewable** — it can be committed, diffed, and audited, or deliberately - excluded by an explicit, reviewable decision rather than by accident. - -Whether .agent/ itself is committed is a separate, per-project decision -(`cortex-agent track` / `cortex-agent untrack`). This rule is about *where the -artifact lives*, not about whether the directory is under version control: an -artifact in .agent/ is reachable across sessions either way, and an artifact in -/tmp is not. - -The same applies to **scripts**: a probe, a fixture generator, or a one-off -measurement script is a reusable asset, not scratch. Write it under `.agent/` -next to the artifacts it produces, so the next person can re-run it. - ---- - -## Where things go - - .agent/ - ├── runtime-evidence/ # feature-produced runtime state - │ └── /. # per-run output, per-sample observations - ├── debug/ # AI debug artifacts (not feature state) - │ ├── screenshots/ # UI screenshots - │ ├── logs/ # temporary server / browser / command logs - │ └── temp/ # scratch JSON, probe output - ├── missions// - │ ├── evidence/ # long-lived verification evidence - │ └── scripts/ # reusable probe / measurement scripts - ├── skills//runs/ # a skill own per-run output - └── metrics/ # health / self-check snapshots - -### Choosing a directory - -| Situation | Path | Example | -| :--- | :--- | :--- | -| Feature runtime state (per-run, per-sample) | `.agent/runtime-evidence//.` | `.agent/runtime-evidence/telemetry/run-20261028.jsonl` | -| AI debug output (screenshots, scratch, logs) | `.agent/debug//` | `.agent/debug/screenshots/login-error-20261028.png` | -| Evidence tied to a mission | `.agent/missions//evidence/` | `.agent/missions/M-025/evidence/P-008-attempt.json` | -| A reusable probe or measurement script | `.agent/missions//scripts/` or `.agent/skills//scripts/` | `.agent/missions/M-044/scripts/acl-win/privilege-probe.ps1` | -| A skill own per-run output | `.agent/skills//runs/` | `.agent/skills/retrieval-trajectory/runs/T-DEMO-001.jsonl` | - -If no existing directory fits, create `.agent/runtime-evidence//`. -Do **not** create a new top-level directory outside `.agent/`. - ---- - -## Naming - -Use `.`: - -- ``: `R--`, or a feature-defined stable id - (`pipeline-20261028-174532`). -- ``: the natural stable id of the sample (assessment id, receipt id), - so records correlate across runs. -- Scripts: kebab-case, describing what they measure - (`privilege-context-probe.ps1`, `probe-07-toctou-and-pin.cjs`). - ---- - -## What not to do - -- ❌ **Do not write runtime artifacts or scripts to /tmp** (or os.tmpdir(), - %TEMP%, $TMPDIR). They are reclaimed by the system and lost with the session. -- ❌ **Do not write them to the project root.** It pollutes git status and makes - them invisible to the next session. -- ❌ **Do not write them to ~/.agent/ or ~/.cache/.** Those are host-private - caches; they do not travel with the project. -- ❌ **Do not add runtime artifacts to .agent/.gitignore.** That deletes them from - the project state directory. If a directory is too noisy, use a retention - policy or size-based archive, and make that choice explicit. -- ❌ **Do not treat .agent/debug/ as release evidence.** Long-lived evidence - belongs in .agent/missions//evidence/ or the project documentation assets. - -### The one legitimate exception - -Short-lived **process plumbing** may use the OS temp directory, provided it is -genuinely ephemeral and holds nothing that must survive the process: - -- a private context file passed to a child process and deleted right after; -- a scratch build directory for a throwaway compile; -- a test fixture directory created and removed inside a single test case. - -The test is simple: **if the artifact has value after the process exits, it -belongs under .agent/.** A secret-bearing context file also belongs nowhere -persistent — it must be mode 0600 and removed on completion, which is why it is -not a runtime artifact but process plumbing. - ---- - -## Fixing a violation - -If an artifact or script was written to the wrong place: - -1. Create the correct directory under .agent/. -2. Move the source script output path (usually one or two - path.join(root, ...) calls) and its input path. -3. Update the test expected path. -4. git mv the old files to preserve history. -5. Re-run the tests, then land the change on its own. - -Do **not** fix this by adding a .gitignore entry — that hides the symptom and -leaves the artifact lost. - ---- - -## Cross-references - -- .agent/rules/ai-debug-artifacts.md — the dedicated .agent/debug/ space for - AI debug output that is not feature state. -- .agent/rules/runtime-state-integration.md — the contract every stateful - feature must satisfy, including where its evidence lives. -- .agent/rules/core-principles.md — the summary line that points here. diff --git a/templates/general/AGENTS.md b/templates/general/AGENTS.md index afbf7620..4c361794 100644 --- a/templates/general/AGENTS.md +++ b/templates/general/AGENTS.md @@ -24,10 +24,9 @@ When you start a session in this project, load in this order: 4. `.agent/general/skills/` — capability implementations backing the workflows 5. `.agent/general/sub-agents/` — long-running sub-agents (e.g. `memory-curator`) 6. `.agent/general/domains/` — scenario-specific configuration -7. `.agent/rules/runtime-artifacts-location.md` — where runtime artifacts - (logs, run output, transcripts, scripts) may be written. Everything the - agent produces at runtime belongs under `.agent/`, never in `/tmp` or the - project root. +7. `.agent/rules/runtime-artifacts-location.md` — how to place logs, run output, + transcripts, and scripts by lifetime. Cross-command material belongs under + `.agent/`; process-local scratch must be removed and verified within the command. ## Data layer (shared, 11 directories) diff --git a/templates/zh/.agent/rules/core-principles.md b/templates/zh/.agent/rules/core-principles.md index fd82de3d..2e6d7d5f 100644 --- a/templates/zh/.agent/rules/core-principles.md +++ b/templates/zh/.agent/rules/core-principles.md @@ -17,7 +17,7 @@ status: stable 1. **遵守语言设定**: 始终遵循 `.agent/rules/language.md` 中定义的语言进行交流。 2. **架构第一**: 在提出任何代码更改建议之前,请务必检查 `.agent/rules/architecture-design.md`。 3. **行为规则**: 会话开始时读取 `.agent/rules/ai-behavior.md` —— 它管理 Git 纪律、编辑范围、代码探索策略(含 `graphify-out/graph.json` 存在时优先使用 Graphify)以及分阶段提交协议。 -4. **运行时产物落盘**: 凡需持久化的运行时产物(单次运行 JSON、单样本观测、调试日志、临时 JSON、探针脚本),一律落在 `.agent/` 下,详见 `.agent/rules/runtime-artifacts-location.md`。**不得**写入 `/tmp`、home 目录或项目根——那些位置会随会话或系统清理而丢失。 +4. **运行时产物落盘**: 按生命周期决定位置。跨命令、复核或交接后仍需读取的 JSON、日志、截图和脚本必须落在 `.agent/` 下;只有同一顶层命令内创建并验证删除的进程临时文件可以使用系统临时目录。详见 `.agent/rules/runtime-artifacts-location.md`。 5. **Memory 索引**: 会话开始时已由 SessionStart hook 自动加载 `.agent/memory/MEMORY.md`(≤200 行 / 25KB cap);按需 Read topic 文件(`{user,feedback,project,reference}/*.md`)。详见 `.agent/rules/memory-protocol.md`。 6. **提案目录结构**: 创建或重组架构提案前读取 `.agent/rules/proposal-structure.md`,尤其是大项目或关联项目提案组。 7. **遵循工作流**: 当用户运行命令(例如 `/start-task`)时,严格遵循 `.agent/workflows/` 中定义的相应工作流。 diff --git a/templates/zh/.agent/rules/runtime-artifacts-location.md b/templates/zh/.agent/rules/runtime-artifacts-location.md index 3f1fd8db..3dca1b6f 100644 --- a/templates/zh/.agent/rules/runtime-artifacts-location.md +++ b/templates/zh/.agent/rules/runtime-artifacts-location.md @@ -1,6 +1,6 @@ --- title: "运行时产物落盘位置" -description: '> cortex-agent 管理的项目里,agent 运行与调试产生的一切产物——日志、探针脚本、单次运行输出、观测数据、临时 JSON——必须落在项目的 .agent/ 目录下,不得写入 /tmp、home 目录或项目根。' +description: '按脚本、日志和输出的生命周期决定位置。跨命令、复核或交接后仍需存在的产物必须放在 .agent/;同进程临时文件只有在同一顶层命令内删除并验证后,才可使用系统临时目录。' type: rule scope: L1 applicable_to: @@ -8,16 +8,43 @@ applicable_to: linked_workflows: [] linked_skills: [] owner: Kucell -last_verified: 2026-10-28 +last_verified: 2026-11-07 status: stable linked_decisions: ["D-OKF-001"] --- # 运行时产物落盘位置 -> **核心规则**:在 cortex-agent 管理的项目里,agent 运行、调试、排查过程中产生的一切产物 -> ——日志、探针脚本、单次运行输出、观测数据、临时 JSON、截图——都必须落在项目的 -> `.agent/` 目录下。**不得**写入 `/tmp`、home 目录或项目根。 +> **核心规则**:按生命周期决定脚本、日志和输出的位置。只要一个文件需要在产生它的 +> 进程退出后继续存在,或会被另一条命令、复核、交接或后续任务引用,它就必须进入项目 +> 的 `.agent/` 目录。临时目录只允许承载真正一次性的进程管道。 +> +> **跨命令即持久化**:用 `apply_patch` 创建脚本、把路径记入后续命令、让另一个 agent +> 读取结果,都会使该文件成为跨命令产物。这类文件不得放在 `/tmp`、`os.tmpdir()`、 +> `%TEMP%` 或 `$TMPDIR`。 + +准确性由三个问题共同决定,不以文件扩展名或目录名单独判定: + +1. 它是否需要在当前进程或当前顶层命令结束后继续存在? +2. 它是否会被后续命令、复核者、其他 agent 或任务续接读取? +3. 它是否是一次可复用的探针、测量或交付资产? + +任一答案为“是”,就按下面的 `.agent/` 生命周期表落盘。 + +--- + +## 生命周期与位置 + +| 生命周期 / 用途 | 位置 | 收口要求 | +|---|---|---| +| 只在单个进程或单个顶层命令内有效 | OS 临时目录 | 同一顶层命令内删除并验证不存在 | +| 跨命令或短期复核需要,但不是长期证据 | `.agent/debug/temp/-/` | 建立文件清单,记录保留条件 | +| 可跨任务复用的脚本 | `.agent/skills//scripts/` | 提供入口与测试 | +| Mission 专项脚本或长期证据 | `.agent/missions//scripts/` 或 `evidence/` | 关联源码版本与验证结果 | +| 给其他 agent 读取的机器可读摘要 | `.agent/artifacts/` | 保存紧凑摘要、引用与哈希 | + +禁止只改文件名来规避规则。位置由生命周期和消费方式决定,不由 `temp`、`scratch` +或 `debug` 这类词决定。 --- @@ -99,18 +126,44 @@ linked_decisions: ["D-OKF-001"] - ❌ **不要把 `.agent/debug/` 当发布证据目录。** 长期证据应放在 `.agent/missions//evidence/` 或项目文档资产目录。 -### 唯一正当的例外 +### 跨命令边界 + +一次“顶层命令”是指宿主发起的一次不可分割执行,并包含它在同一进程树中同步启动的 +所有子进程。以下情况都已经跨过了这个边界: + +- `apply_patch`、`Write`、`Edit` 创建文件后,再由另一条工具命令使用; +- 命令 A 创建脚本或输出,命令 B 通过绝对路径读取、上传、转发或继续修改; +- shell 重定向、`tee`、`cp`、`mv` 或程序参数把文件写到临时目录,但清理发生在 + 另一条命令; +- 把临时路径写进任务进度、handoff、README、配置或其他持久文档。 + +跨命令材料必须直接创建在 `.agent/`。对于需要短期复核的探针和输出,使用 +`.agent/debug/temp/-/`,并在该目录的 `inventory.json` 中记录每个 +文件的用途和保留条件。 -短命的**进程管道**(process plumbing)可以用系统临时目录,前提是它确实用完即弃、 -不承载任何需要在进程退出后仍然存在的内容: +### 单命令临时文件 -- 传给子进程的私有 context 文件,用完立即删除; -- 一次性编译用的临时构建目录; -- 单个测试用例内部创建并清理的 fixture 目录。 +进程管道可以使用系统临时目录,但必须同时满足: -判据很简单:**产物在进程退出后还有价值,就必须落在 `.agent/` 下。** -含密钥的 context 文件也不该持久化——它必须是 `0600` 权限并在结束时删除, -这正是它属于进程管道而不是运行时产物的原因。 +- 文件生命周期严格包含在当前顶层命令的可证明清理路径内; +- 使用 `trap`、`finally` 或等价机制,在成功、失败和中断路径都清理; +- 命令结束前验证路径已不存在; +- 临时路径不得写入持久文档、配置或需要由后续命令读取的变量表。 + +适用于:子进程私有 context 文件(权限 `0600`)、一次性编译目录、单个测试用例内部 +的 fixture。不适用于需要在多条命令间反复复用的 PowerShell、Python、Node 或 shell +探针。 + +### 任务结束收口 + +任务结束前必须盘点本轮创建的所有临时路径,并将每项归入以下一种状态: + +- `deleted`:已删除并验证不存在; +- `promoted`:已迁入 `.agent/debug/temp/` 或更长期位置,并更新全部引用; +- `owned_by_live_process`:仍由明确登记的运行中进程占用,记录进程与保留条件。 + +存在未分类路径时,收口状态为 `TEMP_ARTIFACT_CLOSEOUT_INCOMPLETE`。不得把 +“以后会清理”当作完成状态。 --- @@ -128,6 +181,20 @@ linked_decisions: ["D-OKF-001"] --- +## 机械检查 + +`pnpm check:runtime-artifact-location` 检查 L1 模板、技能、工作流和框架代码中可能把 +产物写入临时目录的写法。检查器识别 Node 写文件 API、`apply_patch` 的 +`Add/Update File` 目标、shell 重定向、`tee`、`cp`、`mv` 和常见 `--output` 参数; +无法判断生命周期的写法会 fail closed,由作者改为明确的 `.agent/` 路径或补充有理由 +的例外。 + +宿主若提供 Stop 事件,应在结束前核对本轮临时路径并为每项记录 `deleted`、`promoted` +或 `owned_by_live_process`。宿主没有可阻断的 Stop 门禁时,同样的核对仍是任务完成 +条件,且必须把检查结果写入任务报告。 + +--- + ## 交叉引用 - `.agent/rules/ai-debug-artifacts.md` —— `.agent/debug/` 是 AI 调试产物的专用位置, diff --git a/tests/distribution/check-runtime-artifact-location.test.js b/tests/distribution/check-runtime-artifact-location.test.js index 92e67a47..2987eedf 100644 --- a/tests/distribution/check-runtime-artifact-location.test.js +++ b/tests/distribution/check-runtime-artifact-location.test.js @@ -72,6 +72,66 @@ test("an artifact directory created under a temp root fails", (t) => { assert.match(result.stdout, /artifact-dir-under-temp/); }); +test("an apply_patch target in a temp directory fails", (t) => { + const root = buildFixture(t, { + "scripts/launch.ps1": [ + "*** Begin Patch", + "*** Add File: /private/tmp/probe/launch-runtime.ps1", + "+Write-Output 'started'", + "*** End Patch", + "", + ].join("\n"), + }); + const result = runCheck(root); + assert.equal(result.status, 1); + assert.match(result.stdout, /apply-patch-target-in-temp/); +}); + +test("shell redirection to a temp path fails", (t) => { + const root = buildFixture(t, { + "scripts/run.sh": "printf 'ready\\n' > /tmp/runtime-status.txt\n", + }); + const result = runCheck(root); + assert.equal(result.status, 1); + assert.match(result.stdout, /shell-write-to-temp/); +}); + +test("tee, copy, and output-flag destinations in temp directories fail", (t) => { + const root = buildFixture(t, { + "scripts/run.sh": [ + "cat runtime.log | tee /tmp/runtime.log", + "cp runtime.ps1 /tmp/probe/runtime.ps1", + "node collect.js --output /var/folders/demo/result.json", + "", + ].join("\n"), + }); + const result = runCheck(root); + assert.equal(result.status, 1); + assert.match(result.stdout, /shell-write-to-temp[^\n]*\(3\)/); +}); + +test("same-command shell scratch with trap cleanup passes", (t) => { + const root = buildFixture(t, { + "scripts/run.sh": [ + "work=$(mktemp -d)", + "trap 'rm -rf \"$work\"' EXIT", + "printf 'ready\\n' > \"$work/status.txt\"", + "test ! -e \"$work\" || true", + "", + ].join("\n"), + }); + const result = runCheck(root); + assert.equal(result.status, 0, `expected pass, got:\n${result.stdout}${result.stderr}`); +}); + +test("promoting a temp script into .agent is allowed", (t) => { + const root = buildFixture(t, { + "scripts/promote.sh": "mv /tmp/probe.ps1 .agent/debug/temp/task-r1/probe.ps1\n", + }); + const result = runCheck(root); + assert.equal(result.status, 0, `expected pass, got:\n${result.stdout}${result.stderr}`); +}); + test("a document that points an agent at a temp artifact path fails", (t) => { const root = buildFixture(t, { "SKILL.md": "Write the summary to /tmp/session-summary.json when done.\n",