scene-capability-engine 3.6.61 → 3.6.63
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 +10 -0
- package/README.md +3 -3
- package/README.zh.md +3 -3
- package/docs/command-reference.md +0 -3
- package/docs/magicball-task-feedback-timeline-guide.md +0 -1
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.62.md +32 -0
- package/docs/releases/v3.6.63.md +34 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.62.md +32 -0
- package/docs/zh/releases/v3.6.63.md +34 -0
- package/lib/commands/studio.js +1 -319
- package/package.json +1 -1
- package/scripts/steering-content-audit.js +15 -0
- package/template/.sce/README.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.6.63] - 2026-03-21
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Relaxed `audit:steering` so a clean-room checkout no longer hard-fails release validation just because the gitignored personal file `.sce/steering/CURRENT_CONTEXT.md` is absent.
|
|
14
|
+
|
|
15
|
+
## [3.6.62] - 2026-03-21
|
|
16
|
+
|
|
17
|
+
### Removed
|
|
18
|
+
- Removed the OpenHands-specific `sce studio events --openhands-events <path>` bridge so current SCE no longer exposes a vendor-specific event-ingestion path in the active CLI surface.
|
|
19
|
+
|
|
10
20
|
## [3.6.61] - 2026-03-20
|
|
11
21
|
|
|
12
22
|
### Added
|
package/README.md
CHANGED
|
@@ -155,7 +155,7 @@ SCE is opinionated by default.
|
|
|
155
155
|
For IDEs, AI shells, or custom frontends, the most important SCE surfaces are:
|
|
156
156
|
|
|
157
157
|
- `sce studio plan|generate|apply|verify|release`
|
|
158
|
-
- `sce studio events
|
|
158
|
+
- `sce studio events`
|
|
159
159
|
- `sce task ref|show|rerun`
|
|
160
160
|
- `sce timeline save|list|show|restore`
|
|
161
161
|
- `sce capability inventory`
|
|
@@ -230,5 +230,5 @@ MIT. See [LICENSE](LICENSE).
|
|
|
230
230
|
|
|
231
231
|
---
|
|
232
232
|
|
|
233
|
-
**Version**: 3.6.
|
|
234
|
-
**Last Updated**: 2026-03-
|
|
233
|
+
**Version**: 3.6.63
|
|
234
|
+
**Last Updated**: 2026-03-21
|
package/README.zh.md
CHANGED
|
@@ -160,7 +160,7 @@ SCE 默认是强治理的。
|
|
|
160
160
|
如果你要对接 IDE、AI 助手或前端,优先关注这些接口面:
|
|
161
161
|
|
|
162
162
|
- `sce studio plan|generate|apply|verify|release`
|
|
163
|
-
- `sce studio events
|
|
163
|
+
- `sce studio events`
|
|
164
164
|
- `sce task ref|show|rerun`
|
|
165
165
|
- `sce timeline save|list|show|restore`
|
|
166
166
|
- `sce capability inventory`
|
|
@@ -235,5 +235,5 @@ MIT,见 [LICENSE](LICENSE)。
|
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
238
|
-
**版本**:3.6.
|
|
239
|
-
**最后更新**:2026-03-
|
|
238
|
+
**版本**:3.6.63
|
|
239
|
+
**最后更新**:2026-03-21
|
|
@@ -744,8 +744,6 @@ sce studio resume --job <job-id> --json
|
|
|
744
744
|
|
|
745
745
|
# Inspect recent stage events
|
|
746
746
|
sce studio events --job <job-id> --limit 50 --json
|
|
747
|
-
# Map OpenHands raw events into the same task-stream contract
|
|
748
|
-
sce studio events --job <job-id> --openhands-events ./openhands-events.json --json
|
|
749
747
|
|
|
750
748
|
# Rollback a job after apply/release
|
|
751
749
|
sce studio rollback --job <job-id> --reason "manual-check-failed" --json
|
|
@@ -779,7 +777,6 @@ Studio JSON output now includes a stable UI-oriented task stream contract (in ad
|
|
|
779
777
|
- `task.evidence[]`: structured evidence references
|
|
780
778
|
- `task.feedback_model`: human-facing task feedback (`problem`, `execution`, `diagnosis`, `evidence`, `next_step`, `mb_status`)
|
|
781
779
|
- `event[]`: raw audit event stream (`studio events` also keeps legacy `events[]` for compatibility)
|
|
782
|
-
- `studio events --openhands-events <path>` switches `source_stream=openhands` and maps OpenHands raw events to the same task contract fields.
|
|
783
780
|
- hierarchical task reference lookup/rerun:
|
|
784
781
|
- `sce task show --ref <SS.PP.TT> --json`
|
|
785
782
|
- `sce task rerun --ref <SS.PP.TT> [--dry-run] --json`
|
|
@@ -117,7 +117,6 @@ SCE 现在在时间线命令中增加:
|
|
|
117
117
|
## 4. 最小接口清单
|
|
118
118
|
|
|
119
119
|
- `sce studio events --job <job-id> --json`
|
|
120
|
-
- `sce studio events --job <job-id> --openhands-events <path> --json`
|
|
121
120
|
- `sce timeline list --limit 20 --json`
|
|
122
121
|
- `sce timeline show <snapshot-id> --json`
|
|
123
122
|
|
package/docs/releases/README.md
CHANGED
|
@@ -9,6 +9,8 @@ This directory stores release-facing documents:
|
|
|
9
9
|
## Archived Versions
|
|
10
10
|
|
|
11
11
|
- [Release checklist](../release-checklist.md)
|
|
12
|
+
- [v3.6.63 release notes](./v3.6.63.md)
|
|
13
|
+
- [v3.6.62 release notes](./v3.6.62.md)
|
|
12
14
|
- [v3.6.61 release notes](./v3.6.61.md)
|
|
13
15
|
- [v3.6.60 release notes](./v3.6.60.md)
|
|
14
16
|
- [v3.6.59 release notes](./v3.6.59.md)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# v3.6.62 Release Notes
|
|
2
|
+
|
|
3
|
+
Release date: 2026-03-21
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Removed the OpenHands-specific `sce studio events --openhands-events <path>` bridge from the tracked CLI surface so SCE no longer publishes a vendor-specific event-ingestion path as a first-class runtime contract.
|
|
8
|
+
- Removed the corresponding OpenHands mapping test coverage and cleaned the main command reference plus README surfaces to keep shipped documentation aligned with the active product boundary.
|
|
9
|
+
- Kept the broader external-runtime direction vendor-neutral so future IDE embedding can focus on Codex CLI, Claude Code, and similar in-process tool endpoints without carrying a stale OpenHands-first entry point.
|
|
10
|
+
|
|
11
|
+
## Validation
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/commands/studio.test.js --runInBand`
|
|
14
|
+
- `npm run test:release`
|
|
15
|
+
- `npm run audit:release-docs`
|
|
16
|
+
- `npm run audit:steering`
|
|
17
|
+
- `npm run test:skip-audit`
|
|
18
|
+
- `npm run test:sce-tracking`
|
|
19
|
+
- `npm run gate:npm-runtime-assets`
|
|
20
|
+
- `npm run test:brand-consistency`
|
|
21
|
+
- `npm run audit:clarification-first`
|
|
22
|
+
- `npm run audit:magicball-engineering-contract`
|
|
23
|
+
- `npm run audit:magicball-project-contract`
|
|
24
|
+
- `npm run gate:collab-governance`
|
|
25
|
+
- `npm run gate:errorbook-registry-health`
|
|
26
|
+
- `npm run gate:errorbook-release`
|
|
27
|
+
- `npm pack --dry-run`
|
|
28
|
+
|
|
29
|
+
## Release Notes
|
|
30
|
+
|
|
31
|
+
- Use `v3.6.62` when you need the published SCE package to exclude the old OpenHands-specific `studio events` bridge from the active CLI and doc surface.
|
|
32
|
+
- This patch intentionally does not rewrite user-local draft Specs; it only cleans the tracked engine, tests, and release-facing documentation.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# v3.6.63 Release Notes
|
|
2
|
+
|
|
3
|
+
Release date: 2026-03-21
|
|
4
|
+
|
|
5
|
+
## Highlights
|
|
6
|
+
|
|
7
|
+
- Fixed a clean-room release blocker in `audit:steering`: the gitignored personal file `.sce/steering/CURRENT_CONTEXT.md` is now treated as optional when absent in a fresh checkout.
|
|
8
|
+
- Added unit coverage so steering governance still enforces stable layers while no longer requiring a local-only context file to exist in Git-tracked release environments.
|
|
9
|
+
- Kept the `v3.6.62` OpenHands cleanup intact; this patch only repairs the release path uncovered while publishing that cleanup.
|
|
10
|
+
|
|
11
|
+
## Validation
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/scripts/steering-content-audit.test.js --runInBand`
|
|
14
|
+
- `npx jest tests/unit/commands/studio.test.js --runInBand`
|
|
15
|
+
- `npm run test:release`
|
|
16
|
+
- `npm run audit:release-docs`
|
|
17
|
+
- `npm run audit:steering`
|
|
18
|
+
- `npm run test:skip-audit`
|
|
19
|
+
- `npm run test:sce-tracking`
|
|
20
|
+
- `npm run gate:npm-runtime-assets`
|
|
21
|
+
- `npm run test:brand-consistency`
|
|
22
|
+
- `npm run audit:clarification-first`
|
|
23
|
+
- `npm run audit:magicball-engineering-contract`
|
|
24
|
+
- `npm run audit:magicball-project-contract`
|
|
25
|
+
- `npm run gate:collab-governance`
|
|
26
|
+
- `npm run gate:errorbook-registry-health`
|
|
27
|
+
- `npm run gate:errorbook-release`
|
|
28
|
+
- `npm run report:interactive-governance -- --fail-on-alert`
|
|
29
|
+
- `npm pack --dry-run`
|
|
30
|
+
|
|
31
|
+
## Release Notes
|
|
32
|
+
|
|
33
|
+
- Use `v3.6.63` if you need the OpenHands cleanup from `v3.6.62` plus a clean-room-safe steering audit during publish.
|
|
34
|
+
- This fix preserves the co-work rule that `CURRENT_CONTEXT.md` remains local/personal state and should not be force-committed just to satisfy release automation.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# v3.6.62 发布说明
|
|
2
|
+
|
|
3
|
+
发布日期:2026-03-21
|
|
4
|
+
|
|
5
|
+
## 重点变化
|
|
6
|
+
|
|
7
|
+
- 从已跟踪 CLI 正式接口面移除了 OpenHands 专有的 `sce studio events --openhands-events <path>` 桥接入口,避免 SCE 继续把某个供应商专有事件摄取路径作为一等运行时契约发布。
|
|
8
|
+
- 同步移除了对应的 OpenHands 映射测试,并清理了主命令参考与 README 中的相关说明,保证随包发布的文档与当前正式产品边界一致。
|
|
9
|
+
- 外部 runtime 方向继续保持 vendor-neutral,为后续 IDE 内嵌 Codex CLI、Claude Code 等执行端预留稳定空间,而不是继续背着过时的 OpenHands-first 入口。
|
|
10
|
+
|
|
11
|
+
## 验证
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/commands/studio.test.js --runInBand`
|
|
14
|
+
- `npm run test:release`
|
|
15
|
+
- `npm run audit:release-docs`
|
|
16
|
+
- `npm run audit:steering`
|
|
17
|
+
- `npm run test:skip-audit`
|
|
18
|
+
- `npm run test:sce-tracking`
|
|
19
|
+
- `npm run gate:npm-runtime-assets`
|
|
20
|
+
- `npm run test:brand-consistency`
|
|
21
|
+
- `npm run audit:clarification-first`
|
|
22
|
+
- `npm run audit:magicball-engineering-contract`
|
|
23
|
+
- `npm run audit:magicball-project-contract`
|
|
24
|
+
- `npm run gate:collab-governance`
|
|
25
|
+
- `npm run gate:errorbook-registry-health`
|
|
26
|
+
- `npm run gate:errorbook-release`
|
|
27
|
+
- `npm pack --dry-run`
|
|
28
|
+
|
|
29
|
+
## 发布说明
|
|
30
|
+
|
|
31
|
+
- 当你需要已发布的 SCE 包不再暴露旧的 OpenHands 专有 `studio events` 桥接入口时,请使用 `v3.6.62`。
|
|
32
|
+
- 本次补丁刻意不去重写用户本地未纳管的草稿 spec,只清理已跟踪的 engine、测试与发布文档面。
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# v3.6.63 发布说明
|
|
2
|
+
|
|
3
|
+
发布日期:2026-03-21
|
|
4
|
+
|
|
5
|
+
## 重点变化
|
|
6
|
+
|
|
7
|
+
- 修复了 `audit:steering` 的 clean-room 发布阻断问题:在全新克隆中,如果被 `.gitignore` 排除的个人态文件 `.sce/steering/CURRENT_CONTEXT.md` 不存在,不再直接把发布校验打成失败。
|
|
8
|
+
- 增加了对应单测,确保 steering 治理仍然会继续严格检查稳定层文件,但不再强迫 Git 跟踪环境必须带着本地个人上下文文件。
|
|
9
|
+
- `v3.6.62` 的 OpenHands 清理内容保持不变;本补丁只修复发布那条线在真实发布过程中暴露出的流程缺陷。
|
|
10
|
+
|
|
11
|
+
## 验证
|
|
12
|
+
|
|
13
|
+
- `npx jest tests/unit/scripts/steering-content-audit.test.js --runInBand`
|
|
14
|
+
- `npx jest tests/unit/commands/studio.test.js --runInBand`
|
|
15
|
+
- `npm run test:release`
|
|
16
|
+
- `npm run audit:release-docs`
|
|
17
|
+
- `npm run audit:steering`
|
|
18
|
+
- `npm run test:skip-audit`
|
|
19
|
+
- `npm run test:sce-tracking`
|
|
20
|
+
- `npm run gate:npm-runtime-assets`
|
|
21
|
+
- `npm run test:brand-consistency`
|
|
22
|
+
- `npm run audit:clarification-first`
|
|
23
|
+
- `npm run audit:magicball-engineering-contract`
|
|
24
|
+
- `npm run audit:magicball-project-contract`
|
|
25
|
+
- `npm run gate:collab-governance`
|
|
26
|
+
- `npm run gate:errorbook-registry-health`
|
|
27
|
+
- `npm run gate:errorbook-release`
|
|
28
|
+
- `npm run report:interactive-governance -- --fail-on-alert`
|
|
29
|
+
- `npm pack --dry-run`
|
|
30
|
+
|
|
31
|
+
## 发布说明
|
|
32
|
+
|
|
33
|
+
- 如果你既需要 `v3.6.62` 的 OpenHands 清理,又需要 clean-room 发布环境下可通过的 steering 审计,请使用 `v3.6.63`。
|
|
34
|
+
- 该修复保持 co-work 规则不变:`CURRENT_CONTEXT.md` 依然属于本地个人态文件,不应为了过发布门禁而被强制提交到 Git。
|
package/lib/commands/studio.js
CHANGED
|
@@ -921,273 +921,6 @@ function extractEventArrayFromPayload(payload) {
|
|
|
921
921
|
return [payload];
|
|
922
922
|
}
|
|
923
923
|
|
|
924
|
-
async function readOpenHandsEventsFile(openhandsEventsPath, fileSystem = fs) {
|
|
925
|
-
const normalizedPath = normalizeString(openhandsEventsPath);
|
|
926
|
-
if (!normalizedPath) {
|
|
927
|
-
return [];
|
|
928
|
-
}
|
|
929
|
-
const exists = await fileSystem.pathExists(normalizedPath);
|
|
930
|
-
if (!exists) {
|
|
931
|
-
throw new Error(`OpenHands events file not found: ${normalizedPath}`);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const content = await fileSystem.readFile(normalizedPath, 'utf8');
|
|
935
|
-
const trimmed = `${content || ''}`.trim();
|
|
936
|
-
if (!trimmed) {
|
|
937
|
-
return [];
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
try {
|
|
941
|
-
const parsed = JSON.parse(trimmed);
|
|
942
|
-
return extractEventArrayFromPayload(parsed);
|
|
943
|
-
} catch (_error) {
|
|
944
|
-
const lines = trimmed
|
|
945
|
-
.split(/\r?\n/)
|
|
946
|
-
.map((line) => line.trim())
|
|
947
|
-
.filter(Boolean);
|
|
948
|
-
const parsedLines = [];
|
|
949
|
-
for (const line of lines) {
|
|
950
|
-
try {
|
|
951
|
-
parsedLines.push(JSON.parse(line));
|
|
952
|
-
} catch (_innerError) {
|
|
953
|
-
// Skip malformed JSONL lines to keep ingestion robust.
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
return parsedLines;
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function normalizeOpenHandsEventRecord(rawEvent, index, jobId) {
|
|
961
|
-
const raw = rawEvent && typeof rawEvent === 'object'
|
|
962
|
-
? rawEvent
|
|
963
|
-
: { value: rawEvent };
|
|
964
|
-
const eventId = pickFirstString([
|
|
965
|
-
raw.event_id,
|
|
966
|
-
raw.eventId,
|
|
967
|
-
raw.id,
|
|
968
|
-
raw.uuid
|
|
969
|
-
]) || `oh-evt-${index + 1}`;
|
|
970
|
-
const eventType = pickFirstString([
|
|
971
|
-
raw.event_type,
|
|
972
|
-
raw.eventType,
|
|
973
|
-
raw.type,
|
|
974
|
-
raw.kind,
|
|
975
|
-
raw.action
|
|
976
|
-
]) || 'unknown';
|
|
977
|
-
const timestamp = pickFirstString([
|
|
978
|
-
raw.timestamp,
|
|
979
|
-
raw.time,
|
|
980
|
-
raw.created_at,
|
|
981
|
-
raw.createdAt,
|
|
982
|
-
raw.ts
|
|
983
|
-
]) || nowIso();
|
|
984
|
-
return {
|
|
985
|
-
api_version: STUDIO_EVENT_API_VERSION,
|
|
986
|
-
event_id: eventId,
|
|
987
|
-
job_id: jobId,
|
|
988
|
-
event_type: `openhands.${eventType}`,
|
|
989
|
-
timestamp,
|
|
990
|
-
metadata: {
|
|
991
|
-
source: 'openhands',
|
|
992
|
-
raw
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
function tryParseJsonText(value) {
|
|
998
|
-
if (typeof value !== 'string') {
|
|
999
|
-
return null;
|
|
1000
|
-
}
|
|
1001
|
-
const trimmed = value.trim();
|
|
1002
|
-
if (!trimmed) {
|
|
1003
|
-
return null;
|
|
1004
|
-
}
|
|
1005
|
-
if (!(trimmed.startsWith('{') || trimmed.startsWith('['))) {
|
|
1006
|
-
return null;
|
|
1007
|
-
}
|
|
1008
|
-
try {
|
|
1009
|
-
return JSON.parse(trimmed);
|
|
1010
|
-
} catch (_error) {
|
|
1011
|
-
return null;
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function stringifyCompact(value) {
|
|
1016
|
-
if (value == null) {
|
|
1017
|
-
return '';
|
|
1018
|
-
}
|
|
1019
|
-
if (typeof value === 'string') {
|
|
1020
|
-
return value;
|
|
1021
|
-
}
|
|
1022
|
-
try {
|
|
1023
|
-
return JSON.stringify(value);
|
|
1024
|
-
} catch (_error) {
|
|
1025
|
-
return `${value}`;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
function extractOpenHandsCommandCandidates(raw = {}) {
|
|
1030
|
-
const command = pickFirstString([
|
|
1031
|
-
raw.command,
|
|
1032
|
-
raw.cmd,
|
|
1033
|
-
raw.input && raw.input.command,
|
|
1034
|
-
raw.action && raw.action.command,
|
|
1035
|
-
raw.observation && raw.observation.command,
|
|
1036
|
-
raw.tool_input && raw.tool_input.command
|
|
1037
|
-
]);
|
|
1038
|
-
|
|
1039
|
-
const toolName = pickFirstString([
|
|
1040
|
-
raw.tool_name,
|
|
1041
|
-
raw.toolName,
|
|
1042
|
-
raw.tool && raw.tool.name,
|
|
1043
|
-
raw.name
|
|
1044
|
-
]);
|
|
1045
|
-
const toolArgs = raw.arguments || raw.tool_input || raw.input || null;
|
|
1046
|
-
|
|
1047
|
-
const cmd = command || (toolName
|
|
1048
|
-
? `tool:${toolName}${toolArgs ? ` ${stringifyCompact(toolArgs)}` : ''}`
|
|
1049
|
-
: '');
|
|
1050
|
-
|
|
1051
|
-
return {
|
|
1052
|
-
cmd,
|
|
1053
|
-
exit_code: pickFirstNumber([
|
|
1054
|
-
raw.exit_code,
|
|
1055
|
-
raw.exitCode,
|
|
1056
|
-
raw.result && (raw.result.exit_code ?? raw.result.exitCode)
|
|
1057
|
-
]),
|
|
1058
|
-
stdout: pickFirstString([
|
|
1059
|
-
raw.stdout,
|
|
1060
|
-
raw.output,
|
|
1061
|
-
raw.result && raw.result.stdout,
|
|
1062
|
-
raw.observation && raw.observation.stdout
|
|
1063
|
-
]),
|
|
1064
|
-
stderr: pickFirstString([
|
|
1065
|
-
raw.stderr,
|
|
1066
|
-
raw.result && raw.result.stderr,
|
|
1067
|
-
raw.error && raw.error.message,
|
|
1068
|
-
typeof raw.error === 'string' ? raw.error : ''
|
|
1069
|
-
]),
|
|
1070
|
-
log_path: pickFirstString([
|
|
1071
|
-
raw.log_path,
|
|
1072
|
-
raw.logPath,
|
|
1073
|
-
raw.log && raw.log.path
|
|
1074
|
-
])
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
function collectOpenHandsFileChangesFromRaw(raw = {}) {
|
|
1079
|
-
const changes = [];
|
|
1080
|
-
const addPath = (pathValue, lineValue, diffRefValue = '') => {
|
|
1081
|
-
const pathRef = normalizeString(pathValue);
|
|
1082
|
-
if (!pathRef) {
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
const line = Number.parseInt(`${lineValue != null ? lineValue : 1}`, 10);
|
|
1086
|
-
const normalizedLine = Number.isFinite(line) && line > 0 ? line : 1;
|
|
1087
|
-
const diffRef = normalizeString(diffRefValue) || `${pathRef}:${normalizedLine}`;
|
|
1088
|
-
changes.push({
|
|
1089
|
-
path: pathRef,
|
|
1090
|
-
line: normalizedLine,
|
|
1091
|
-
diffRef
|
|
1092
|
-
});
|
|
1093
|
-
};
|
|
1094
|
-
|
|
1095
|
-
if (typeof raw.path === 'string') {
|
|
1096
|
-
addPath(raw.path, raw.line || raw.line_number, raw.diff_ref);
|
|
1097
|
-
}
|
|
1098
|
-
if (raw.file && typeof raw.file === 'object') {
|
|
1099
|
-
addPath(raw.file.path || raw.file.name, raw.file.line || raw.file.line_number, raw.file.diffRef || raw.file.diff_ref);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
const arrayKeys = ['files', 'changed_files', 'modified_files', 'created_files', 'deleted_files'];
|
|
1103
|
-
for (const key of arrayKeys) {
|
|
1104
|
-
const values = Array.isArray(raw[key]) ? raw[key] : [];
|
|
1105
|
-
for (const value of values) {
|
|
1106
|
-
if (typeof value === 'string') {
|
|
1107
|
-
addPath(value, 1);
|
|
1108
|
-
} else if (value && typeof value === 'object') {
|
|
1109
|
-
addPath(value.path || value.file || value.name, value.line || value.line_number, value.diffRef || value.diff_ref);
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
const patchText = pickFirstString([raw.patch, raw.diff]);
|
|
1115
|
-
if (patchText) {
|
|
1116
|
-
const parsedJson = tryParseJsonText(patchText);
|
|
1117
|
-
if (parsedJson && typeof parsedJson === 'object') {
|
|
1118
|
-
const nested = collectOpenHandsFileChangesFromRaw(parsedJson);
|
|
1119
|
-
changes.push(...nested);
|
|
1120
|
-
} else {
|
|
1121
|
-
const lines = patchText.split(/\r?\n/);
|
|
1122
|
-
for (const line of lines) {
|
|
1123
|
-
const match = line.match(/^\+\+\+\s+b\/(.+)$/);
|
|
1124
|
-
if (match && match[1]) {
|
|
1125
|
-
addPath(match[1], 1);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
return changes;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
function mapOpenHandsEventsToTaskSignals(openhandsEvents = [], context = {}) {
|
|
1135
|
-
const normalizedEvents = openhandsEvents.map((item, index) =>
|
|
1136
|
-
normalizeOpenHandsEventRecord(item, index, context.jobId)
|
|
1137
|
-
);
|
|
1138
|
-
|
|
1139
|
-
const commands = [];
|
|
1140
|
-
const fileChanges = [];
|
|
1141
|
-
const errors = [];
|
|
1142
|
-
const evidence = [];
|
|
1143
|
-
|
|
1144
|
-
for (const event of normalizedEvents) {
|
|
1145
|
-
const raw = event && event.metadata ? event.metadata.raw || {} : {};
|
|
1146
|
-
const commandCandidate = extractOpenHandsCommandCandidates(raw);
|
|
1147
|
-
if (commandCandidate.cmd || commandCandidate.stdout || commandCandidate.stderr || commandCandidate.exit_code != null) {
|
|
1148
|
-
commands.push(commandCandidate);
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
fileChanges.push(...collectOpenHandsFileChangesFromRaw(raw));
|
|
1152
|
-
|
|
1153
|
-
const eventType = normalizeString(event.event_type).toLowerCase();
|
|
1154
|
-
const failedByType = eventType.includes('error') || eventType.includes('fail');
|
|
1155
|
-
const failedByExit = commandCandidate.exit_code != null && Number(commandCandidate.exit_code) !== 0;
|
|
1156
|
-
const failedByStderr = commandCandidate.stderr.length > 0 && !commandCandidate.stdout;
|
|
1157
|
-
if (failedByType || failedByExit || failedByStderr) {
|
|
1158
|
-
const message = pickFirstString([
|
|
1159
|
-
raw.message,
|
|
1160
|
-
raw.error && raw.error.message,
|
|
1161
|
-
typeof raw.error === 'string' ? raw.error : '',
|
|
1162
|
-
failedByExit ? `OpenHands command failed (exit ${commandCandidate.exit_code})` : '',
|
|
1163
|
-
'OpenHands event indicates failure'
|
|
1164
|
-
]);
|
|
1165
|
-
errors.push({
|
|
1166
|
-
message,
|
|
1167
|
-
step_id: event.event_type,
|
|
1168
|
-
cmd: commandCandidate.cmd,
|
|
1169
|
-
exit_code: commandCandidate.exit_code,
|
|
1170
|
-
stderr: commandCandidate.stderr,
|
|
1171
|
-
stdout: commandCandidate.stdout
|
|
1172
|
-
});
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
|
-
evidence.push({
|
|
1176
|
-
type: 'openhands-event',
|
|
1177
|
-
ref: event.event_id,
|
|
1178
|
-
detail: event.event_type
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
return {
|
|
1183
|
-
event: normalizedEvents,
|
|
1184
|
-
commands: normalizeTaskCommands(commands),
|
|
1185
|
-
file_changes: normalizeTaskFileChanges(fileChanges),
|
|
1186
|
-
errors: normalizeTaskErrors(errors),
|
|
1187
|
-
evidence: normalizeTaskEvidence(evidence)
|
|
1188
|
-
};
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
924
|
function extractCommandsFromStageMetadata(stageMetadata = {}) {
|
|
1192
925
|
const gateSteps = Array.isArray(stageMetadata && stageMetadata.gate_steps)
|
|
1193
926
|
? stageMetadata.gate_steps
|
|
@@ -2919,21 +2652,7 @@ async function runStudioEventsCommand(options = {}, dependencies = {}) {
|
|
|
2919
2652
|
|
|
2920
2653
|
const limit = normalizePositiveInteger(options.limit, 50);
|
|
2921
2654
|
const job = await loadJob(paths, jobId, fileSystem);
|
|
2922
|
-
const openhandsEventsPath = normalizeString(options.openhandsEvents);
|
|
2923
|
-
let sourceStream = 'studio';
|
|
2924
2655
|
let events = await readStudioEvents(paths, jobId, { limit }, fileSystem);
|
|
2925
|
-
let openhandsSignals = null;
|
|
2926
|
-
if (openhandsEventsPath) {
|
|
2927
|
-
const absoluteOpenhandsPath = path.isAbsolute(openhandsEventsPath)
|
|
2928
|
-
? openhandsEventsPath
|
|
2929
|
-
: path.join(projectPath, openhandsEventsPath);
|
|
2930
|
-
const rawOpenhandsEvents = await readOpenHandsEventsFile(absoluteOpenhandsPath, fileSystem);
|
|
2931
|
-
openhandsSignals = mapOpenHandsEventsToTaskSignals(rawOpenhandsEvents, {
|
|
2932
|
-
jobId: job.job_id
|
|
2933
|
-
});
|
|
2934
|
-
events = openhandsSignals.event;
|
|
2935
|
-
sourceStream = 'openhands';
|
|
2936
|
-
}
|
|
2937
2656
|
|
|
2938
2657
|
const payload = await buildCommandPayload('studio-events', job, {
|
|
2939
2658
|
events,
|
|
@@ -2941,47 +2660,11 @@ async function runStudioEventsCommand(options = {}, dependencies = {}) {
|
|
|
2941
2660
|
fileSystem
|
|
2942
2661
|
});
|
|
2943
2662
|
payload.limit = limit;
|
|
2944
|
-
payload.source_stream =
|
|
2945
|
-
if (sourceStream === 'openhands') {
|
|
2946
|
-
payload.openhands_events_file = path.relative(projectPath, path.isAbsolute(openhandsEventsPath)
|
|
2947
|
-
? openhandsEventsPath
|
|
2948
|
-
: path.join(projectPath, openhandsEventsPath)).replace(/\\/g, '/');
|
|
2949
|
-
payload.task = {
|
|
2950
|
-
...payload.task,
|
|
2951
|
-
summary: [
|
|
2952
|
-
`OpenHands events: ${events.length} | commands: ${openhandsSignals.commands.length} | errors: ${openhandsSignals.errors.length}`,
|
|
2953
|
-
`File changes: ${openhandsSignals.file_changes.length} | source: ${payload.openhands_events_file}`,
|
|
2954
|
-
`Next: ${payload.task.next_action}`
|
|
2955
|
-
],
|
|
2956
|
-
handoff: {
|
|
2957
|
-
...(payload.task.handoff || {}),
|
|
2958
|
-
source_stream: 'openhands',
|
|
2959
|
-
openhands_event_count: events.length,
|
|
2960
|
-
openhands_command_count: openhandsSignals.commands.length,
|
|
2961
|
-
openhands_error_count: openhandsSignals.errors.length
|
|
2962
|
-
},
|
|
2963
|
-
commands: openhandsSignals.commands,
|
|
2964
|
-
file_changes: openhandsSignals.file_changes,
|
|
2965
|
-
errors: openhandsSignals.errors,
|
|
2966
|
-
evidence: normalizeTaskEvidence([
|
|
2967
|
-
...(Array.isArray(payload.task.evidence) ? payload.task.evidence : []),
|
|
2968
|
-
...openhandsSignals.evidence,
|
|
2969
|
-
{
|
|
2970
|
-
type: 'openhands-event-file',
|
|
2971
|
-
ref: payload.openhands_events_file,
|
|
2972
|
-
detail: 'mapped'
|
|
2973
|
-
}
|
|
2974
|
-
])
|
|
2975
|
-
};
|
|
2976
|
-
payload.eventId = events.length > 0
|
|
2977
|
-
? events[events.length - 1].event_id
|
|
2978
|
-
: null;
|
|
2979
|
-
}
|
|
2663
|
+
payload.source_stream = 'studio';
|
|
2980
2664
|
payload.events = events;
|
|
2981
2665
|
const normalizedPayload = attachTaskFeedbackModel(payload);
|
|
2982
2666
|
printStudioEventsPayload(normalizedPayload, options);
|
|
2983
2667
|
return normalizedPayload;
|
|
2984
|
-
return payload;
|
|
2985
2668
|
}
|
|
2986
2669
|
|
|
2987
2670
|
function printStudioIntakePayload(payload, options = {}) {
|
|
@@ -3267,7 +2950,6 @@ function registerStudioCommands(program) {
|
|
|
3267
2950
|
.description('Show studio job event stream')
|
|
3268
2951
|
.option('--job <job-id>', 'Studio job id (defaults to latest)')
|
|
3269
2952
|
.option('--limit <number>', 'Maximum number of recent events to return', '50')
|
|
3270
|
-
.option('--openhands-events <path>', 'Optional OpenHands raw events file (.json/.jsonl) mapped to task stream')
|
|
3271
2953
|
.option('--json', 'Print machine-readable JSON output')
|
|
3272
2954
|
.action(async (options) => runStudioCommand(runStudioEventsCommand, options, 'events'));
|
|
3273
2955
|
|
package/package.json
CHANGED
|
@@ -186,6 +186,10 @@ function pushViolation(violations, severity, file, rule, message, suggestion) {
|
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
function isOptionalCurrentContextFile(fileName) {
|
|
190
|
+
return fileName === 'CURRENT_CONTEXT.md';
|
|
191
|
+
}
|
|
192
|
+
|
|
189
193
|
function auditSteeringContent(options = {}) {
|
|
190
194
|
const projectPath = path.resolve(options.projectPath || process.cwd());
|
|
191
195
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
@@ -206,6 +210,17 @@ function auditSteeringContent(options = {}) {
|
|
|
206
210
|
for (const fileName of fileNames) {
|
|
207
211
|
const absolutePath = path.join(steeringDir, fileName);
|
|
208
212
|
if (!fs.existsSync(absolutePath)) {
|
|
213
|
+
if (isOptionalCurrentContextFile(fileName)) {
|
|
214
|
+
pushViolation(
|
|
215
|
+
violations,
|
|
216
|
+
'warning',
|
|
217
|
+
fileName,
|
|
218
|
+
'missing_optional_context',
|
|
219
|
+
`${fileName} is absent in this checkout; clean-room repositories may omit personal current context until active local work begins.`,
|
|
220
|
+
'Create CURRENT_CONTEXT.md locally when active work starts, but keep it out of Git-tracked release state.'
|
|
221
|
+
);
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
209
224
|
pushViolation(
|
|
210
225
|
violations,
|
|
211
226
|
'error',
|
package/template/.sce/README.md
CHANGED
|
@@ -243,6 +243,6 @@ A Spec is a complete feature definition with three parts:
|
|
|
243
243
|
---
|
|
244
244
|
|
|
245
245
|
**Project Type**: Spec-driven development
|
|
246
|
-
**sce Version**: 3.6.
|
|
247
|
-
**Last Updated**: 2026-03-
|
|
246
|
+
**sce Version**: 3.6.63
|
|
247
|
+
**Last Updated**: 2026-03-21
|
|
248
248
|
**Purpose**: Guide AI tools to work effectively with this project
|