codebee 0.1.95 → 0.1.97
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/CHANGELOG.md +733 -720
- package/LICENSE +21 -21
- package/README.md +516 -509
- package/app/core/acceptance.py +100 -100
- package/app/core/agent_context.py +118 -0
- package/app/core/aiflavor.py +197 -197
- package/app/core/analytics.py +38 -38
- package/app/core/artifacts.py +148 -148
- package/app/core/assets.py +110 -110
- package/app/core/attachments.py +623 -623
- package/app/core/audit.py +30 -30
- package/app/core/backup.py +470 -470
- package/app/core/beekeeper.py +183 -183
- package/app/core/benchstore.py +102 -95
- package/app/core/board.py +246 -209
- package/app/core/bookmeta.py +1036 -1036
- package/app/core/bookmeta_catalog.py +180 -180
- package/app/core/branching.py +137 -137
- package/app/core/builtin_agent.py +2055 -1680
- package/app/core/capability.py +144 -144
- package/app/core/chaptersafe.py +32 -32
- package/app/core/checkpoints.py +648 -0
- package/app/core/cleanup.py +303 -303
- package/app/core/compaction.py +201 -201
- package/app/core/contracts.py +255 -255
- package/app/core/covergen.py +356 -356
- package/app/core/defectretro.py +43 -43
- package/app/core/diagnostics.py +114 -114
- package/app/core/dispatch.py +198 -191
- package/app/core/dispatch_log.py +128 -128
- package/app/core/env_scrub.py +85 -85
- package/app/core/errorlog.py +137 -137
- package/app/core/eval_matrix.py +82 -0
- package/app/core/evalbench.py +184 -91
- package/app/core/evaluation.py +174 -174
- package/app/core/failure_notify.py +290 -290
- package/app/core/flow_graph.py +33 -0
- package/app/core/goal_service.py +167 -167
- package/app/core/health.py +389 -389
- package/app/core/history.py +38 -38
- package/app/core/hooks.py +162 -162
- package/app/core/jobs.py +25 -4
- package/app/core/knowledge_pipeline.py +86 -0
- package/app/core/manager.py +2232 -2232
- package/app/core/mcp_client.py +200 -29
- package/app/core/mocks.py +64 -64
- package/app/core/notify.py +201 -201
- package/app/core/novel_quality.py +181 -189
- package/app/core/openapi.py +177 -154
- package/app/core/operations.py +228 -228
- package/app/core/paihang.py +145 -145
- package/app/core/pipeline.py +319 -59
- package/app/core/planner.py +17 -11
- package/app/core/policy.py +84 -0
- package/app/core/portguard.py +111 -111
- package/app/core/presence.py +77 -77
- package/app/core/preview.py +293 -293
- package/app/core/project_memory.py +139 -0
- package/app/core/prompt_guard.py +78 -78
- package/app/core/publish/__init__.py +7 -7
- package/app/core/publish/auto.py +106 -22
- package/app/core/publish/browser.py +694 -655
- package/app/core/publish/chaprows.py +138 -0
- package/app/core/publish/fanqie.py +212 -165
- package/app/core/publish/flow.py +832 -640
- package/app/core/publish/flows-fanqie-calibrated.json +361 -336
- package/app/core/publish/ledger.py +51 -2
- package/app/core/publish/manager.py +1150 -1118
- package/app/core/publish/qimao.py +176 -176
- package/app/core/publish/volumes.py +108 -0
- package/app/core/publish/ws.py +139 -139
- package/app/core/quality_gate.py +194 -194
- package/app/core/redact.py +38 -38
- package/app/core/repeat_guard.py +129 -129
- package/app/core/retrieval.py +137 -137
- package/app/core/router.py +398 -398
- package/app/core/runner.py +2239 -2238
- package/app/core/selfupdate.py +384 -384
- package/app/core/session_log.py +161 -161
- package/app/core/sessions.py +312 -312
- package/app/core/settings.py +33 -5
- package/app/core/settings_schema.py +282 -271
- package/app/core/share_page.py +114 -114
- package/app/core/skill_scan.py +139 -139
- package/app/core/skillpacks/fanqie-novel.md +8 -6
- package/app/core/skillpacks/market/character-bible.md +66 -66
- package/app/core/skillpacks/market/code-risk-checklist.md +58 -58
- package/app/core/skillpacks/market/git-workflow.md +57 -57
- package/app/core/skillpacks/market/release-notes.md +72 -72
- package/app/core/skillpacks/market/weekly-report.md +71 -71
- package/app/core/skillpacks/market/worldview-consistency.md +70 -70
- package/app/core/skillpacks/qimao-signing.md +14 -11
- package/app/core/skills.py +32 -21
- package/app/core/step_runner.py +132 -132
- package/app/core/store.py +24 -0
- package/app/core/story_tracking.py +275 -275
- package/app/core/task_compile.py +199 -199
- package/app/core/telemetry.py +292 -292
- package/app/core/tlsctx.py +64 -64
- package/app/core/token_meter.py +147 -147
- package/app/core/tool_outputs.py +107 -0
- package/app/core/tracing.py +207 -144
- package/app/core/upstream.py +41 -41
- package/app/core/usage.py +1038 -1038
- package/app/core/volumes.py +389 -389
- package/app/core/webhooks.py +89 -0
- package/app/core/wxdigest.py +865 -858
- package/app/core/zentao.py +2692 -2692
- package/app/main.py +179 -10
- package/app/mcp_server.py +244 -244
- package/app/pick_dialog.py +110 -110
- package/app/ui/app.js +563 -21
- package/app/ui/artdiff.js +74 -74
- package/app/ui/assets/community-qr.png +0 -0
- package/app/ui/assets/donate-alipay.jpg +0 -0
- package/app/ui/assets/donate-qr.jpg +0 -0
- package/app/ui/assets/workbench-reference.png +0 -0
- package/app/ui/board.css +451 -666
- package/app/ui/board.html +165 -162
- package/app/ui/board.js +509 -305
- package/app/ui/hive3d.js +242 -0
- package/app/ui/i18n.js +125 -9
- package/app/ui/icons/bee.svg +79 -79
- package/app/ui/index.html +60 -3
- package/app/ui/manifest.json +16 -16
- package/app/ui/qrcode.js +2297 -2297
- package/app/ui/style.css +189 -9
- package/bin/tutti.js +147 -147
- package/package.json +49 -48
- package/tools/wx_reader.py +261 -261
- package/app/.playwright-cli/console-2026-09-23T08-26-03-635Z.log +0 -88
- package/app/.playwright-cli/page-2026-09-23T08-26-03-845Z.yml +0 -126
- package/app/.playwright-cli/page-2026-09-23T08-29-44-203Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-35-42-046Z.yml +0 -210
- package/app/.playwright-cli/page-2026-09-23T08-36-52-858Z.png +0 -0
- package/app/.playwright-cli/page-2026-09-23T08-37-33-938Z.png +0 -0
- package/app/.playwright-cli/run-detail-after.png +0 -0
- package/app/output/_createbook_body.json +0 -1
- package/app/output/_probe_fq_click.py +0 -50
- package/app/output/_probe_fq_form.py +0 -42
- package/app/output/_probe_fq_modal.py +0 -55
- package/app/output/_probe_fq_points.py +0 -71
- package/app/output/_probe_fq_submit.py +0 -34
- package/app/output/_probe_fq_watch.py +0 -62
package/app/core/acceptance.py
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
"""Run acceptance matrix and evidence normalization.
|
|
3
|
-
|
|
4
|
-
The matrix keeps correctness, performance, boundary behavior, exception
|
|
5
|
-
handling, readability and security as separate conclusions. Missing evidence
|
|
6
|
-
is reported as ``not_evaluated``/``unknown`` rather than being inferred from a
|
|
7
|
-
successful process exit.
|
|
8
|
-
"""
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import time
|
|
12
|
-
|
|
13
|
-
CASES = ("correctness", "performance", "boundary", "exception",
|
|
14
|
-
"readability", "security")
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def _case(name, status, evidence=None, reason=""):
|
|
18
|
-
return {"case": name, "status": status,
|
|
19
|
-
"evidence": [evidence] if isinstance(evidence, dict) else [],
|
|
20
|
-
"reason": str(reason or "")[:300]}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _quality_verdict(run):
|
|
24
|
-
verdict = run.get("verdict") or {}
|
|
25
|
-
for key in ("pass", "publishable", "verify_pass"):
|
|
26
|
-
if isinstance(verdict.get(key), bool):
|
|
27
|
-
return verdict.get(key), key
|
|
28
|
-
return None, ""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def evaluate_run(run, task=None):
|
|
32
|
-
"""Build a deterministic acceptance result from persisted run evidence."""
|
|
33
|
-
run = run or {}
|
|
34
|
-
task = task or {}
|
|
35
|
-
status = str(run.get("status") or "")
|
|
36
|
-
verdict = run.get("verdict") or {}
|
|
37
|
-
out = []
|
|
38
|
-
|
|
39
|
-
correct, source = _quality_verdict(run)
|
|
40
|
-
if status in ("failed", "timeout", "cancelled"):
|
|
41
|
-
out.append(_case("correctness", "unknown",
|
|
42
|
-
{"source": "run.status", "value": status},
|
|
43
|
-
"任务终态未形成可判定的质量结论"))
|
|
44
|
-
elif correct is not None:
|
|
45
|
-
out.append(_case("correctness", "passed" if correct else "failed",
|
|
46
|
-
{"source": "run.verdict", "field": source,
|
|
47
|
-
"value": bool(correct)}))
|
|
48
|
-
else:
|
|
49
|
-
out.append(_case("correctness", "not_evaluated", reason="缺少验证或质量门证据"))
|
|
50
|
-
|
|
51
|
-
duration = run.get("duration_s")
|
|
52
|
-
if duration is None and run.get("started_at") and run.get("ended_at"):
|
|
53
|
-
duration = "recorded"
|
|
54
|
-
has_duration = isinstance(duration, (int, float)) and duration >= 0
|
|
55
|
-
has_usage = any(isinstance(run.get(key), (int, float)) and run.get(key) > 0
|
|
56
|
-
for key in ("tokens", "cost_usd"))
|
|
57
|
-
if has_duration or has_usage:
|
|
58
|
-
out.append(_case("performance", "passed", {
|
|
59
|
-
"source": "run.metrics", "duration_s": duration,
|
|
60
|
-
"tokens": run.get("tokens", 0), "cost_usd": run.get("cost_usd", 0),
|
|
61
|
-
}, "仅表示性能数据已记录,不代表达到业务阈值"))
|
|
62
|
-
else:
|
|
63
|
-
out.append(_case("performance", "not_evaluated", reason="缺少耗时或用量证据"))
|
|
64
|
-
|
|
65
|
-
boundary = verdict.get("boundary_checks")
|
|
66
|
-
if isinstance(boundary, list) and boundary and all(
|
|
67
|
-
isinstance(x, dict) and isinstance(x.get("passed"), bool) for x in boundary):
|
|
68
|
-
failed = [x for x in boundary if x.get("passed") is False]
|
|
69
|
-
out.append(_case("boundary", "failed" if failed else "passed",
|
|
70
|
-
{"source": "run.verdict.boundary_checks", "count": len(boundary),
|
|
71
|
-
"failed": len(failed)}))
|
|
72
|
-
else:
|
|
73
|
-
out.append(_case("boundary", "not_evaluated", reason="当前任务未声明边界检查证据"))
|
|
74
|
-
|
|
75
|
-
exception = verdict.get("exception")
|
|
76
|
-
if isinstance(exception, bool):
|
|
77
|
-
out.append(_case("exception", "passed" if exception else "failed",
|
|
78
|
-
{"source": "run.verdict.exception", "value": exception}))
|
|
79
|
-
elif status in ("failed", "timeout"):
|
|
80
|
-
out.append(_case("exception", "failed" if status in ("failed", "timeout") else "unknown",
|
|
81
|
-
{"source": "run.error", "status": status,
|
|
82
|
-
"error": str(run.get("error") or "")[:300]}))
|
|
83
|
-
elif status == "cancelled":
|
|
84
|
-
out.append(_case("exception", "unknown",
|
|
85
|
-
{"source": "run.status", "value": status}))
|
|
86
|
-
else:
|
|
87
|
-
out.append(_case("exception", "not_evaluated", reason="缺少独立异常处理证据"))
|
|
88
|
-
|
|
89
|
-
for name, key in (("readability", "readability"), ("security", "security")):
|
|
90
|
-
value = verdict.get(key)
|
|
91
|
-
if isinstance(value, bool):
|
|
92
|
-
out.append(_case(name, "passed" if value else "failed",
|
|
93
|
-
{"source": "run.verdict.%s" % key, "value": value}))
|
|
94
|
-
else:
|
|
95
|
-
out.append(_case(name, "not_evaluated", reason="未提供独立 %s 证据" % name))
|
|
96
|
-
|
|
97
|
-
# 总体通过要求六个维度都有明确通过证据;缺证据不能被两个局部绿灯掩盖。
|
|
98
|
-
return {"version": 1, "evaluated_at": time.time(),
|
|
99
|
-
"passed": bool(out and all(x["status"] == "passed" for x in out)),
|
|
100
|
-
"cases": out}
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Run acceptance matrix and evidence normalization.
|
|
3
|
+
|
|
4
|
+
The matrix keeps correctness, performance, boundary behavior, exception
|
|
5
|
+
handling, readability and security as separate conclusions. Missing evidence
|
|
6
|
+
is reported as ``not_evaluated``/``unknown`` rather than being inferred from a
|
|
7
|
+
successful process exit.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
CASES = ("correctness", "performance", "boundary", "exception",
|
|
14
|
+
"readability", "security")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _case(name, status, evidence=None, reason=""):
|
|
18
|
+
return {"case": name, "status": status,
|
|
19
|
+
"evidence": [evidence] if isinstance(evidence, dict) else [],
|
|
20
|
+
"reason": str(reason or "")[:300]}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _quality_verdict(run):
|
|
24
|
+
verdict = run.get("verdict") or {}
|
|
25
|
+
for key in ("pass", "publishable", "verify_pass"):
|
|
26
|
+
if isinstance(verdict.get(key), bool):
|
|
27
|
+
return verdict.get(key), key
|
|
28
|
+
return None, ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def evaluate_run(run, task=None):
|
|
32
|
+
"""Build a deterministic acceptance result from persisted run evidence."""
|
|
33
|
+
run = run or {}
|
|
34
|
+
task = task or {}
|
|
35
|
+
status = str(run.get("status") or "")
|
|
36
|
+
verdict = run.get("verdict") or {}
|
|
37
|
+
out = []
|
|
38
|
+
|
|
39
|
+
correct, source = _quality_verdict(run)
|
|
40
|
+
if status in ("failed", "timeout", "cancelled"):
|
|
41
|
+
out.append(_case("correctness", "unknown",
|
|
42
|
+
{"source": "run.status", "value": status},
|
|
43
|
+
"任务终态未形成可判定的质量结论"))
|
|
44
|
+
elif correct is not None:
|
|
45
|
+
out.append(_case("correctness", "passed" if correct else "failed",
|
|
46
|
+
{"source": "run.verdict", "field": source,
|
|
47
|
+
"value": bool(correct)}))
|
|
48
|
+
else:
|
|
49
|
+
out.append(_case("correctness", "not_evaluated", reason="缺少验证或质量门证据"))
|
|
50
|
+
|
|
51
|
+
duration = run.get("duration_s")
|
|
52
|
+
if duration is None and run.get("started_at") and run.get("ended_at"):
|
|
53
|
+
duration = "recorded"
|
|
54
|
+
has_duration = isinstance(duration, (int, float)) and duration >= 0
|
|
55
|
+
has_usage = any(isinstance(run.get(key), (int, float)) and run.get(key) > 0
|
|
56
|
+
for key in ("tokens", "cost_usd"))
|
|
57
|
+
if has_duration or has_usage:
|
|
58
|
+
out.append(_case("performance", "passed", {
|
|
59
|
+
"source": "run.metrics", "duration_s": duration,
|
|
60
|
+
"tokens": run.get("tokens", 0), "cost_usd": run.get("cost_usd", 0),
|
|
61
|
+
}, "仅表示性能数据已记录,不代表达到业务阈值"))
|
|
62
|
+
else:
|
|
63
|
+
out.append(_case("performance", "not_evaluated", reason="缺少耗时或用量证据"))
|
|
64
|
+
|
|
65
|
+
boundary = verdict.get("boundary_checks")
|
|
66
|
+
if isinstance(boundary, list) and boundary and all(
|
|
67
|
+
isinstance(x, dict) and isinstance(x.get("passed"), bool) for x in boundary):
|
|
68
|
+
failed = [x for x in boundary if x.get("passed") is False]
|
|
69
|
+
out.append(_case("boundary", "failed" if failed else "passed",
|
|
70
|
+
{"source": "run.verdict.boundary_checks", "count": len(boundary),
|
|
71
|
+
"failed": len(failed)}))
|
|
72
|
+
else:
|
|
73
|
+
out.append(_case("boundary", "not_evaluated", reason="当前任务未声明边界检查证据"))
|
|
74
|
+
|
|
75
|
+
exception = verdict.get("exception")
|
|
76
|
+
if isinstance(exception, bool):
|
|
77
|
+
out.append(_case("exception", "passed" if exception else "failed",
|
|
78
|
+
{"source": "run.verdict.exception", "value": exception}))
|
|
79
|
+
elif status in ("failed", "timeout"):
|
|
80
|
+
out.append(_case("exception", "failed" if status in ("failed", "timeout") else "unknown",
|
|
81
|
+
{"source": "run.error", "status": status,
|
|
82
|
+
"error": str(run.get("error") or "")[:300]}))
|
|
83
|
+
elif status == "cancelled":
|
|
84
|
+
out.append(_case("exception", "unknown",
|
|
85
|
+
{"source": "run.status", "value": status}))
|
|
86
|
+
else:
|
|
87
|
+
out.append(_case("exception", "not_evaluated", reason="缺少独立异常处理证据"))
|
|
88
|
+
|
|
89
|
+
for name, key in (("readability", "readability"), ("security", "security")):
|
|
90
|
+
value = verdict.get(key)
|
|
91
|
+
if isinstance(value, bool):
|
|
92
|
+
out.append(_case(name, "passed" if value else "failed",
|
|
93
|
+
{"source": "run.verdict.%s" % key, "value": value}))
|
|
94
|
+
else:
|
|
95
|
+
out.append(_case(name, "not_evaluated", reason="未提供独立 %s 证据" % name))
|
|
96
|
+
|
|
97
|
+
# 总体通过要求六个维度都有明确通过证据;缺证据不能被两个局部绿灯掩盖。
|
|
98
|
+
return {"version": 1, "evaluated_at": time.time(),
|
|
99
|
+
"passed": bool(out and all(x["status"] == "passed" for x in out)),
|
|
100
|
+
"cases": out}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Discover repository-local AGENTS.md guidance for an execution context.
|
|
3
|
+
|
|
4
|
+
The format is intentionally compatible with the common AGENTS.md convention,
|
|
5
|
+
but the loader stays small and dependency-free. Guidance is treated as input
|
|
6
|
+
data: it is bounded, scrubbed before prompt injection, and content-addressed so
|
|
7
|
+
the run can explain exactly which context was used.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
from .redact import scrub_text
|
|
16
|
+
|
|
17
|
+
MAX_FILES = 8
|
|
18
|
+
MAX_FILE_CHARS = 8000
|
|
19
|
+
MAX_PROMPT_CHARS = 24000
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _roots(workdir, boundary=None):
|
|
23
|
+
try:
|
|
24
|
+
current = Path(workdir).expanduser().resolve()
|
|
25
|
+
except (TypeError, OSError, ValueError):
|
|
26
|
+
return []
|
|
27
|
+
if not current.is_dir():
|
|
28
|
+
return []
|
|
29
|
+
# Trust instructions only through the nearest repository root. Outside a
|
|
30
|
+
# repository, the selected workdir itself is the boundary; never walk into
|
|
31
|
+
# an unrelated parent directory (or drive root).
|
|
32
|
+
try:
|
|
33
|
+
if boundary:
|
|
34
|
+
stop = Path(boundary).expanduser().resolve()
|
|
35
|
+
else:
|
|
36
|
+
stop = current
|
|
37
|
+
for candidate in (current, *current.parents):
|
|
38
|
+
if (candidate / ".git").exists():
|
|
39
|
+
stop = candidate
|
|
40
|
+
break
|
|
41
|
+
except (TypeError, OSError, ValueError):
|
|
42
|
+
stop = current
|
|
43
|
+
if current != stop and stop not in current.parents:
|
|
44
|
+
stop = current
|
|
45
|
+
roots = []
|
|
46
|
+
while True:
|
|
47
|
+
roots.append(current)
|
|
48
|
+
if current == stop:
|
|
49
|
+
break
|
|
50
|
+
parent = current.parent
|
|
51
|
+
if parent == current:
|
|
52
|
+
break
|
|
53
|
+
current = parent
|
|
54
|
+
return roots
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _read(path):
|
|
58
|
+
try:
|
|
59
|
+
if path.is_symlink() or not path.is_file():
|
|
60
|
+
return ""
|
|
61
|
+
raw = path.read_bytes()[:MAX_FILE_CHARS * 4]
|
|
62
|
+
try:
|
|
63
|
+
text = raw.decode("utf-8")
|
|
64
|
+
except UnicodeDecodeError:
|
|
65
|
+
text = raw.decode("gbk", "replace")
|
|
66
|
+
return scrub_text(text, limit=MAX_FILE_CHARS)
|
|
67
|
+
except OSError:
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def discover(workdir, *, boundary=None, max_files=MAX_FILES, max_chars=MAX_PROMPT_CHARS):
|
|
72
|
+
"""Return nearest-first AGENTS.md guidance and stable metadata."""
|
|
73
|
+
try:
|
|
74
|
+
file_limit = max(1, min(MAX_FILES, int(max_files)))
|
|
75
|
+
char_limit = max(1000, min(MAX_PROMPT_CHARS, int(max_chars)))
|
|
76
|
+
except (TypeError, ValueError):
|
|
77
|
+
file_limit, char_limit = MAX_FILES, MAX_PROMPT_CHARS
|
|
78
|
+
records = []
|
|
79
|
+
chunks = []
|
|
80
|
+
used = 0
|
|
81
|
+
for root in _roots(workdir, boundary=boundary):
|
|
82
|
+
if len(records) >= file_limit:
|
|
83
|
+
break
|
|
84
|
+
path = root / "AGENTS.md"
|
|
85
|
+
text = _read(path)
|
|
86
|
+
if not text:
|
|
87
|
+
continue
|
|
88
|
+
remaining = char_limit - used
|
|
89
|
+
if remaining <= 0:
|
|
90
|
+
break
|
|
91
|
+
text = text[:remaining]
|
|
92
|
+
rel = str(path)
|
|
93
|
+
records.append({
|
|
94
|
+
"path": rel,
|
|
95
|
+
"scope": str(root),
|
|
96
|
+
"sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
|
|
97
|
+
"chars": len(text),
|
|
98
|
+
})
|
|
99
|
+
chunks.append("## AGENTS.md(作用域:%s)\n%s" % (root, text))
|
|
100
|
+
used += len(text)
|
|
101
|
+
prompt = "\n\n".join(chunks)
|
|
102
|
+
digest = hashlib.sha256(prompt.encode("utf-8")).hexdigest()
|
|
103
|
+
return {"prompt": prompt, "files": records, "content_sha256": digest,
|
|
104
|
+
"file_count": len(records), "chars": len(prompt)}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def inject(task, workdir=None):
|
|
108
|
+
"""Append discovered guidance to a task copy and return metadata."""
|
|
109
|
+
value = dict(task or {})
|
|
110
|
+
result = discover(workdir or value.get("workdir"))
|
|
111
|
+
if result["prompt"]:
|
|
112
|
+
existing = str(value.get("context") or "").strip()
|
|
113
|
+
value["context"] = (existing + "\n\n" if existing else "") + result["prompt"]
|
|
114
|
+
value["agent_context"] = {"content_sha256": result["content_sha256"],
|
|
115
|
+
"files": result["files"],
|
|
116
|
+
"file_count": result["file_count"],
|
|
117
|
+
"chars": result["chars"]}
|
|
118
|
+
return value, result
|