okstra 0.193.0 → 0.195.0

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.
@@ -16,9 +16,10 @@ Supported keys (CLI alias -> JSON field):
16
16
  only) relative to <project-root>.
17
17
 
18
18
  report-language -> reportLanguage
19
- Final-report language. One of: en, ko, auto.
20
- 'auto' is resolved by the report-writer lead from
21
- the task brief at run time.
19
+ Final-report language as a language tag
20
+ (en, ko, fr, pt-BR, ...). The Phase 7 translator
21
+ translates the human HTML into it; the data.json
22
+ stays English.
22
23
 
23
24
  Usage:
24
25
  okstra config get <key> [--scope project|global|all]
@@ -66,9 +67,9 @@ const KEYS = {
66
67
  jsonField: "reportLanguage",
67
68
  scopes: ["project", "global"],
68
69
  validate(value) {
69
- const allowed = new Set(["en", "ko", "auto"]);
70
- if (typeof value !== "string" || !allowed.has(value)) {
71
- return (`value must be one of: en, ko, auto (got ${JSON.stringify(value)})`);
70
+ if (typeof value !== "string" || !/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/.test(value)) {
71
+ return ("value must be a language tag such as en, ko, fr or pt-BR " +
72
+ `(got ${JSON.stringify(value)})`);
72
73
  }
73
74
  return null;
74
75
  },
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","sourceRoot":"","sources":["../../../src/commands/lifecycle/config.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config.mjs","sourceRoot":"","sources":["../../../src/commands/lifecycle/config.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAW,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAa,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErF,MAAM,KAAK,GAAG;;;4BAGc,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsCrC,CAAC;AA4BF,MAAM,IAAI,GAAG;IACX,kBAAkB,EAAE;QAClB,SAAS,EAAE,gBAAgB;QAC3B,MAAM,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;QAC7B,QAAQ,CAAC,KAAc,EAAE,GAAwC;YAC/D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;gBACrD,OAAO,gCAAgC,CAAC;YAC1C,CAAC;YACD,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC3B,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;oBAClE,OAAO,CACL,+DAA+D;wBAC/D,QAAQ,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,yDAAyD,CACnF,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF;IACD,iBAAiB,EAAE;QACjB,SAAS,EAAE,gBAAgB;QAC3B,MAAM,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;QAC7B,QAAQ,CAAC,KAAc;YACrB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,kCAAkC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACjF,OAAO,CACL,2DAA2D;oBAC3D,QAAQ,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CACjC,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF;CAC0C,CAAC;AAc9C,SAAS,SAAS,CAAC,IAAuB;IACxC,MAAM,IAAI,GAAkB;QAC1B,EAAE,EAAE,IAAI;QACR,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,IAAI;QACX,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;QAClB,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,KAAK;KACb,CAAC;IACF,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,KAAK,SAAS;YAAE,SAAS;QAC9B,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;YACpB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;YAC5E,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;gBAC9D,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,CAAC,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;YACpD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;YAChB,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACnB,CAAC;aAAM,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACzC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;aAAM,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACvC,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IACD,IAAI,CAAC,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAChC,IAAI,CAAC,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACjC,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,iBAAiB,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,gBAAgB;IACvB,OAAO,IAAI,CAAC,UAAU,EAAE,EAAE,aAAa,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,iBAAiB,CAAC,WAAmB;IAC5C,OAAO,eAAe,CAAC,WAAW,CAAC,CAAC;AACtC,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,CAAS;IAC/B,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,CAAC;QACH,MAAM,IAAI,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAC9C,CAAC,CAAE,IAAgC;YACnC,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,KAAK,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChE,CAAC;AACH,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,CAAS,EAAE,IAAa;IACrD,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;IACtC,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACtE,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC1B,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAC3C,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,SAAS,EACT,CAAC,IAAI,EAAE,8BAA8B,EAAE,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,EACpE,EAAE,UAAU,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CACvC,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACnD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,IAAI,CAAC,IAAmB,EAAE,OAAgB;IACjD,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO;IACvB,IAAI,IAAI,CAAC,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACvC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,uBAAuB,CAAC,WAA0B;IAC/D,qDAAqD;IACrD,6CAA6C;IAC7C,MAAM,KAAK,GAAG,MAAM,YAAY,EAAE,CAAC;IACnC,MAAM,MAAM,GAAG,MAAM,UAAU,CAC7B,SAAS,EACT;QACE,IAAI,EAAE,8BAA8B,EAAE,aAAa;QACnD,gBAAgB,EAAE,WAAW,IAAI,OAAO,CAAC,GAAG,EAAE;KAC/C,EACD,EAAE,UAAU,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CACvC,CAAC;IACF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,IAAwC;IAC3D,MAAM,IAAI,GAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;IAClC,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAEvD,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3C,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,GAAG,CAAC,OAAO,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;QACjF,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACnE,GAAG,CAAC,OAAO,GAAG;gBACZ,SAAS,EAAE,IAAI;gBACf,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC;gBACpC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI;aACnC,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,GAAG,CAAC,MAAM,GAAG;YACX,SAAS,EAAE,IAAI;YACf,IAAI,EAAE,gBAAgB,EAAE;YACxB,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI;SACnC,CAAC;IACJ,CAAC;IAED,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,KAAK,kBAAkB,EAAE,CAAC;QACvD,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAAC,WAAW,CAAC,CAAC;QAC7D,IAAI,SAAS;YAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IAC3C,CAAC;IAED,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAClC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,KAAK,EAAE,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAChB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,IAAwC;IAC3D,MAAM,IAAI,GAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,CAAC,GAAG,6BAA6B,CAAC,CAAC;IAC/F,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACvB,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,gEAAgE;gBAC9D,yCAAyC,CAC5C,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,KAAK,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,GAAG,6BAA6B,KAAK,GAAG,CAAC,CAAC;IACzE,CAAC;IACD,MAAM,GAAG,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC3C,IAAI,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IAE9B,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,UAAU,GACd,KAAK,KAAK,SAAS,IAAI,WAAW,KAAK,IAAI;QACzC,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC;QAChC,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;IACtC,MAAM,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACpF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,IAAwC;IAC7D,MAAM,IAAI,GAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACvB,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;QACvC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,WAAW,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,+DAA+D,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,UAAU,GACd,KAAK,KAAK,SAAS,IAAI,WAAW,KAAK,IAAI;QACzC,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC;QAChC,CAAC,CAAC,gBAAgB,EAAE,CAAC;IACzB,MAAM,QAAQ,GAAG,CAAC,MAAM,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IACpD,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,QAAQ,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACjF,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChC,MAAM,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5C,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAmB;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC;IAClC,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvD,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3C,GAAG,CAAC,OAAO,GAAG,WAAW;YACvB,CAAC,CAAC;gBACE,IAAI,EAAE,iBAAiB,CAAC,WAAW,CAAC;gBACpC,IAAI,EAAE,CAAC,MAAM,QAAQ,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE;aAC7D;YACH,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,kCAAkC,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC1C,GAAG,CAAC,MAAM,GAAG;YACX,IAAI,EAAE,gBAAgB,EAAE;YACxB,IAAI,EAAE,CAAC,MAAM,QAAQ,CAAC,gBAAgB,EAAE,CAAC,CAAC,IAAI,EAAE;SACjD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAuB;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACxE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAClE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QACzD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,EAAE,6BAA6B,CAAC,CAAC;YACtE,OAAO,CAAC,CAAC;QACX,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gBAAgB,IAAI,CAAC,GAAG,iBAAiB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC1E,CAAC;YACF,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,QAAQ,IAAI,CAAC,EAAE,EAAE,CAAC;YAChB,KAAK,KAAK;gBACR,OAAO,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,KAAK,KAAK;gBACR,OAAO,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,KAAK,OAAO;gBACV,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACtC,KAAK,MAAM;gBACT,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5B;gBACE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uBAAuB,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC1D,OAAO,CAAC,CAAC;QACb,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtD,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,4CAA4C;AAC5C,SAAS,OAAO,CAAC,IAAmB;IAClC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,WAAW,CAAC,KAAoB;IACvC,OAAO,KAAK,KAAK,IAAI,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7E,CAAC"}
@@ -863,7 +863,7 @@ The manifest-provided `lead-events-*.jsonl` file is the canonical record for str
863
863
 
864
864
  `activityContractVersion: 1` is an interpretation version for new `implementation-planning` artifacts. A run without that field remains a historical run and does not require activity events or an `agentActivity[]` projection.
865
865
 
866
- The shared `okstra report-finalize` entrypoint reads canonical activity before translation source checking. Under contract v3 its in-process `project-activity` step assembles the report, filtering events by run identity and validating activity ID order before `agentActivity[]` is published. A schema-invalid composed record is still published so `validate-run` can scan it; the step itself fails. A historical v2 manifest retains the in-place projection path. A failed Phase 7 step does not skip later checks: `validate-run` still runs, and only `record-group-memory` and `teardown-stages` are skipped. For a non-English report, the `translate` step between `check-source` and `render-views` materializes and dispatches the translator worker itself (`okstra_ctl.report_translation_dispatch`) and requires the `*.i18n.<lang>.json` sidecar; the lead no longer splits the sequence around a manual dispatch. Conformance compares the resulting `agentActivity[]` IDs, order, and core fields with the canonical events for every lead host.
866
+ The shared `okstra report-finalize` entrypoint reads canonical activity before translation. Under contract v3 its in-process `project-activity` step assembles the report, filtering events by run identity and validating activity ID order before `agentActivity[]` is published. A schema-invalid composed record is still published so `validate-run` can scan it; the step itself fails. A historical v2 manifest retains the in-place projection path. A failed Phase 7 step does not skip later checks: `validate-run` still runs, and only `record-group-memory` and `teardown-stages` are skipped. For a non-English report, the `translate` step directly before `render-views` materializes and dispatches the translator worker itself (`okstra_ctl.report_translation_dispatch`) and requires the `*.i18n.<lang>.json` sidecar; the lead no longer splits the sequence around a manual dispatch. Conformance compares the resulting `agentActivity[]` IDs, order, and core fields with the canonical events for every lead host.
867
867
 
868
868
  Approval blockers use `open`, `answered`, `resolved`, and `obsolete`. `open` blocks until the user judges. `answered` with `accept-risk` / `select` / `answer` does not block approval or the next phase; the DISAGREE votes stay on the plan item as evidence. A response sidecar with a proceeding disposition unblocks the same way. `request-revision` and `reject` still withhold the next phase until this report's `supersessionLedger` records that the answer was incorporated (`superseded` or `no-dependent-statement`).
869
869
 
package/docs/cli.md CHANGED
@@ -801,7 +801,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
801
801
  | `okstra plan-items derivations --data <data.json> --response <user-response sidecar> [--clarification C-NNN]` | List the plan statements an answered clarification may have falsified. Extracts the symbols, paths, and ids the answer names (backticked spans plus `R-001` / `DEV-10174`-style ids) and reports every string in the plan body that mentions one, as a JSON pointer plus excerpt. Advisory: it says where a decision's subject is mentioned, never which mentions are now wrong — the supersession rule (`prompts/profiles/_common-contract.md`) requires the author to enumerate before editing, and this supplies the enumeration |
802
802
  | `okstra plan-items <prepare\|prompt\|validate-prepared> --run-manifest <path> …` | Bind the implementation-planning verification queue to the run manifest. `prepare` extracts the exact queue from `--narrative` and, when `designPreparation.mode` is `no-design-inputs` and the Stage Map has one row, flips `convergence.planBodyVerification.gating` to `false` (stdout `Gating`). `prompt` emits its fixed lossless view, ending with the parser-facing `## Response format` block so the block reaches every verifier with the queue; `validate-prepared` proves the prepared queue still matches the narrative. With `--state <plan-body-verification.json>` the round is a re-verification: `prepare` also carries each queued item's recorded votes and `selfFixNote` into the envelope as `priorRounds`, `prompt` renders them as that item's `**Prior round dissent**` block behind a re-verification preamble, and `validate-prepared` re-derives the carry and rejects an envelope that dropped it. Python resolves the convergence-owned state path, so model callers never choose it. |
803
803
  | `okstra plan-items seed --narrative <report-narrative.md> --state <plan-body-verification.json> [--prior-state <previous plan-body-verification.json>]` | Create the convergence-owned `planBodyVerification.planItems[]` rows every verdict lands in, from the same deterministic extraction `extract` uses. The historical v2 form is `--data <data.json>`. Idempotent by id: an existing row keeps its verdicts and carried fields. Reports `seeded` / `existing` counts. `--prior-state` carries the previous **run**'s verdicts into this one: a newly seeded item whose `contentHash` equals that run's `verifiedContentHash` for the same id inherits its `verdicts[]` and is tagged `carriedForwardFromSeq` with the seq read off the prior filename, so round 1 does not re-judge text nobody changed. A matching id alone never carries — `P-*` ids are positional and shift. It requires `--state`, refuses a prior state whose task root differs from the one `--state` lives under (the state file carries no task identity, so its path is the only identity there is), and when it carries anything it rewrites the sibling `plan-items-*.json` `dispatchQueue` the way `incremental-carry` does. Adds `carried` / `carriedForwardFromSeq` to the reported counts. |
804
- | `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md>… --round <N>` | Read each worker's Markdown verdict directly, validate every current `P-*` id, reject duplicate worker submissions, and overwrite that round's stored verdicts. The historical `--verdicts <file>` form remains automation compatibility only. Without `--append` every recorded verdict row of the queued items is replaced; when a row belongs to a round `complete-round` never closed, the command refuses before writing and names the `complete-round --round <M>` to run first. `--discard-open-rounds` replaces anyway — the recovery path when those rounds are being re-applied from their result files in order (the discarded rows are printed). |
804
+ | `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker-id>=<result.md>… --round <N>` | Read each worker's Markdown verdict directly, validate every current `P-*` id, reject duplicate worker submissions, and overwrite that round's stored verdicts. The historical `--verdicts <file>` form remains automation compatibility only. Without `--append` every recorded verdict row of the queued items is replaced; when a row belongs to a round `complete-round` never closed, the command refuses before writing and names the `complete-round --round <M>` to run first. `--discard-open-rounds` replaces anyway — the recovery path when those rounds are being re-applied from their result files in order (the discarded rows are printed); it also restores `dispatchQueue` to the items those result files answer, since the persisted queue belongs to the latest round and an earlier round's verdicts would otherwise be refused as outside it. |
805
805
  | `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N> [--self-fix-note <item-id>=<markdown-file>]… [--self-fix-group <cause-file>=<item-id>[,<item-id>...]]… [--self-fix-stop-reason <all-resolved\|no-progress\|max-rounds-reached>]` | After `plan-verify` succeeds, atomically derive and record the round's per-item votes, gate result, participant counts from the actual assigned roster, immutable completion time, convergence history, and optional self-fix notes/groups read from Markdown files. `--self-fix-group` requires `--self-fix-stop-reason` — there is no default. `--self-fix-stop-reason` alone records a stop for a round that rewrote nothing and leaves `selfFixGroups` / `selfFixRoundsApplied` untouched. Models do not write the state JSON. Stdout also carries `nextDispatch`. |
806
806
  | `okstra plan-items next-dispatch --state <plan-body-verification.json> [--run-manifest <path>]` | After `apply-verdicts`, decide whether this round opens a worker batch. `kind: none` — missing-dependency `UNVERIFIABLE` only, no new batch. `kind: worker-correction` — re-prompt only those workers; peers stay idle. `kind: critic-tie` — unsettled analyser 1-1 on a run that rostered a critic, `critic-worker` on those item ids only. `kind: user-decision` — the same 1-1 on a run with no critic rostered: no in-band vote can break it, so open one `okstra approval-decision open` per item (classification `noncritical-dissent`) plus its `## 1. Clarification Items` row and dispatch no further verification for them. `--run-manifest` is what tells the two apart (`invocationAssignments` `critic/*`); without it the answer stays `critic-tie`. A missing path is never environment-unverifiable. |
807
807
  | `okstra plan-items correction-prompt --state <plan-body-verification.json> --run-manifest <path> --worker <id>` | Emit the planning-time environment-gap paragraph, then the assigned queue. The environment exception is first. Exits 2 unless `next-dispatch` named that worker as a blanket-UNVERIFIABLE correction target. |
@@ -847,7 +847,7 @@ The `okstra` Node CLI (`bin/okstra`) provides both installer/admin commands and
847
847
  | `okstra approval-decision <open\|resolve\|carry> --ledger <approval-decisions.json> …` | Write the lead-owned clarification and approval ledger. `open` validates classification-specific dispositions and complete option fields, `resolve` requires real `A-NNN` check references, and `carry` keeps prior resolved decisions outside the active clarification list. `carry --from-responses <instruction-set/clarification-response.md>` is the source of truth for an answer given in an earlier run: the bundle is task-level and cumulative, each response section names the report that posed the question, and `--clarification-id` repeats to carry several ids in one call. `carry --source-ledger` remains for a prior run's ledger that is still on disk and needs `--source-run-ref`. Prepare seeds `carriedDecisions[]` itself when it creates a run's ledger from a `--clarification-response` that names a report record — every row that record answered or resolved, plus rows its user-responses sidecars answered (`scripts/okstra_ctl/approval_decisions.py` `seed_carried_decisions`) — so `carry` is for ids that record does not answer. |
848
848
  | `okstra design-snapshot --narrative <report-narrative.md> --output <design-preparation.json>` | Detect implementation-planning design surfaces and write the detector-owned snapshot consumed by final report assembly. |
849
849
  | `okstra plan-verify --narrative <report-narrative.md> --state <plan-body-verification.json>` | Recompute the plan-body gate from the convergence-owned state before `data.json` publication. `--report <historical-data.json>` remains the v2 reader. |
850
- | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, checks the English source, translates (`translate`: for a non-English `reportLanguage` it materializes and dispatches the translator worker unless the `*.i18n.<lang>.json` sidecar already exists, and fails when the worker leaves none), then renders with that sidecar overlaid, spawns follow-ups, validates, records the run's conclusion and the group's start order into the task-group's `group-context.md` (`record-group-memory`, creating the file when absent), and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; `record-group-memory` and `teardown-stages` are skipped so a failed run neither hands an unvalidated conclusion to sibling tasks nor reclaims worktrees. The result carries `nextInGroup` (the first task in start order not yet started) and, for a terminal pointer, `nextCommand` closes on starting it from its brief. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
850
+ | `okstra report-finalize --project-root <dir> --run-manifest <path> --report <final-report.md>` | Run Phase 7 in the manifest's contract order. Contract v3 collects usage into team state, assembles all single-owner inputs into `data.json` once, translates (`translate`: for a non-English `reportLanguage` it materializes and dispatches the translator worker unless the `*.i18n.<lang>.json` sidecar already exists, and fails when the worker leaves none), then renders with that sidecar overlaid, spawns follow-ups, validates, records the run's conclusion and the group's start order into the task-group's `group-context.md` (`record-group-memory`, creating the file when absent), and tears down eligible stage worktrees. Contract v2 retains its historical in-place projection sequence as a read-only compatibility path. A failed step still runs every later check through `validate-run`; `record-group-memory` and `teardown-stages` are skipped so a failed run neither hands an unvalidated conclusion to sibling tasks nor reclaims worktrees. The result carries `nextInGroup` (the first task in start order not yet started) and, for a terminal pointer, `nextCommand` closes on starting it from its brief. Reports each step and prints the ordered `--only` recovery tail from the earliest failure. This is the shared path for every lead adapter. |
851
851
  | `okstra render-views <final-report.data.json\|final-report.md>` | The Phase 7 `render-views` step, runnable on its own. Schema v2 data is rendered directly, and schema v3 data uses the same always-generated, task-specific human HTML path. The full reading copy uses `templates/reports/final-report-v2.template.md` and is rendered on demand with `okstra render-final-report`. Passing the Markdown sibling locates the same data.json. Schema v1 and quick reports keep the legacy conditional renderer. The Node wrapper calls `scripts/okstra-render-report-views.py`; `validators/validate-report-views.py` verifies source/schema/template digests, required human fields, form controls, external assets, diagram/table ID parity, Response ID parity, and that every in-page `href="#…"` lands on an element of the page. For a non-English report the command prints two counts: `translated N string(s) into <lang> (M left in English, K unresolved)` from the sidecar overlay, and `rendered R line(s) still in English on the <lang> page` from the written page itself — the second sees fields the extractor does not offer, so `M = 0` with `R > 0` means a reader-facing key is missing from `PROSE_KEYS`. |
852
852
  | `okstra design-prep <list\|show\|write>` | Review AI-prepared implementation design requests, inspect their effective confirmed response, or append a confirmed user/wizard response without editing the planning report |
853
853
  | `okstra wizard <init\|step\|render-args\|confirmation\|outcome> --state-file <path>` | Interactive input state machine for okstra-run, implemented by `okstra_ctl.wizard`. Seed a state file with `init`, then repeatedly call `step --answer <val>` to receive the next `Prompt` JSON. `--answer` is **required**; use `--no-submit` to peek at the next prompt without submitting a response. A `pick` with more choices than the host picker can display keeps `kind: "pick"` but adds `presentation: "numbered-text"`; render every option as a numbered Markdown list and submit the user's 1-based number, exact value, or exact label. Invalid, out-of-range, and ambiguous answers re-prompt without dropping choices. `render-args` returns the final `render-bundle` argument map, and `confirmation` returns the user echo block. On a completed wizard, `outcome` returns `renderArgs`, `persistActions`, and `confirmationText` together; project/global release-handoff PR-template persistence appears as `persistActions[].command == "config.set"`. For an `implementation` task type, `stage_pick` follows `approved_plan_pick` and selects the stage before `executor_pick`. The brief step appears only for entry task types—requirements-discovery, error-analysis, improvement-discovery, project-analysis, feature-analysis, and change-impact-analysis. Analysis inputs use `feature_evidence_pick` / `feature_evidence`, `project_evidence_pick` / `project_evidence`, and `analysis_target_pick` / `analysis_target`; a revision-requested report prioritizes its same-task, same-type rerun. Downstream lifecycle phases automatically carry the manifest brief, with a three-option `brief_carry` fallback when none is registered; `release-handoff` has no brief and enters multi-select `handoff_stage_pick` for eligible stage groups or the whole task |
@@ -104,7 +104,7 @@ Optional settings:
104
104
  a user-owned advisory when the environment is unavailable or the result is
105
105
  non-PASS.
106
106
  - PR body template: `okstra config set pr-template-path "<path>" --scope project|global`
107
- - final report language: `okstra config set report-language <en|ko|auto> --scope project`
107
+ - final report language: `okstra config set report-language <language-tag> --scope project` (`en`, `ko`, `fr`, `pt-BR`, …; default `en`)
108
108
  - `architecture.style`: the project's declared architecture — `hexagonal`,
109
109
  `layered`, or `none` (default `none` when absent, unrecognized, or
110
110
  unreadable). `okstra setup` never writes it; hand-add it to `project.json`
@@ -345,7 +345,7 @@ Important modules:
345
345
  | `lead_events.py` | Structured JSONL events emitted by artifact-accounted lead runtimes. Its locked append path assigns monotonic `A-NNN` identifiers to activity events in the same canonical event file. |
346
346
  | `worker_prompt_headers.py` | shared rendering of phase-aware worker prompt anchors (`worker_prompt_headers`): coding-preflight only for implementation and compact target identity for final-verification |
347
347
  | `worker_prompt_body.py` | provider-neutral initial analysis body/input renderer shared by Codex and external/team dispatch paths |
348
- | `report_language.py` | report-writer `**Report Language:**` resolution (`resolve_report_language`) — precedence project config → global config → task-brief inference, shared by both dispatchers so the stamped language does not depend on which dispatch path ran |
348
+ | `report_language.py` | report-writer `**Report Language:**` resolution (`resolve_report_language`) — precedence project config → global config → `en`; the value is a language tag the Phase 7 translator translates into, shared by both dispatchers so the stamped language does not depend on which dispatch path ran |
349
349
  | `worker_prompt_policy.py` | `PromptPlan` generating SSOT — resolves functional audience, equality group, packet-only input, coding-preflight eligibility, required headers, and size limits without consulting provider/model identity |
350
350
  | `worker_prompt_contract.py` | deterministic cross-task initial-prompt validator and normalized cross-worker equality SSOT, reused by dispatch adapters and the Phase 7 persisted-artifact validator |
351
351
  | `initial_prompt_materialization.py` | sole owner of roster-derived initial prompt rendering, request compatibility checks, contract validation, and immutable create-if-absent publication; exposes `materialize_initial_prompts(InitialPromptMaterializationRequest) -> tuple[Path, ...]` |
@@ -360,7 +360,7 @@ Important modules:
360
360
  | `scope_provenance.py` | single source of truth for the scope-provenance grammar every phase-emitted requirement must declare, shared by `validators/validate-run.py` and `validators/validate_fanout.py` so the planning report and fan-out packets cannot drift |
361
361
  | `worker_artifact_paths.py` | canonical worker artifact path derivation (e.g. `audit_sidecar_rel` inserts `-audit-` after the first `-worker-` token), so dispatch and validation agree on non-canonical-path rejection |
362
362
  | `report_translation_dispatch.py` | Phase 7 `translate` step — for a non-English `reportLanguage` and no `*.i18n.<lang>.json` sidecar, reuses or materializes this run's translator reservation (`agent-prompt materialize --audience translator` in-process, instruction file under `state/`), runs the CLI-wrapper dispatch, and succeeds only when the sidecar exists afterwards. Replaces the manual lead sequence that was skipped in practice |
363
- | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `check-source` → `translate` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` → `record-group-memory` → `teardown-stages` in that load-bearing order. A non-zero exit still runs every later check through `validate-run` and names the earliest failure; `record-group-memory` (this run's conclusion into the task-group's `group-context.md`, plus `nextInGroup` for the closeout) and `teardown-stages` are skipped when any earlier step failed. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
363
+ | `report_finalize.py` | Phase 7 post-report sequence **SSOT** — runs `translate` → `token-usage` → `render-views` → `spawn-followups` → `validate-run` → `record-group-memory` → `teardown-stages` in that load-bearing order. A non-zero exit still runs every later check through `validate-run` and names the earliest failure; `record-group-memory` (this run's conclusion into the task-group's `group-context.md`, plus `nextInGroup` for the closeout) and `teardown-stages` are skipped when any earlier step failed. Both lead paths converge here: the Codex adapter calls it in-process (`codex_dispatch`), a Claude-led run reaches it through `okstra report-finalize`. Neither reimplements the sequence |
364
364
  | `wrapper_status.py` | worker wrapper status sidecar reader — the host-side reader of the sidecar `worker_runner.py` writes. `is_terminal` is the one question it answers for the dispatch record and the pane reclaim: does `stage` read `exited` |
365
365
  | `worker_runner.py` | runs one worker CLI and records what happened — shared by every provider entrypoint. Owns the `selectors` pump over the child's streams, the stream-arrival idle watchdog (`killpg` on breach), the run-wide progress cap on the log copy, and the status sidecar's whole life. A run that dies after launch still closes its sidecar, so `worker_liveness` never reads a dead worker as running |
366
366
  | `session_transcript.py` | worker session transcript — one line per event (time, speaker, body) with a run-wide progress-line cap (`LOG_LINE_CAP`, elision notice) so a single-file dispatch's tool echo cannot dominate the project's `.okstra/` bytes; the fixed shape lets a later lead write share the same file |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "okstra",
3
- "version": "0.193.0",
3
+ "version": "0.195.0",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.193.0",
3
- "builtAt": "2026-09-09T09:27:49.565Z",
2
+ "package": "0.195.0",
3
+ "builtAt": "2026-09-09T11:10:47.172Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -24,7 +24,7 @@ roster.
24
24
 
25
25
  ## Procedure
26
26
 
27
- 1. Read your dispatch prompt's `**Report Language:**` header. It is already resolved to a concrete language never `auto`.
27
+ 1. Read your dispatch prompt's `**Report Language:**` header. It is a language tag (`ko`, `fr`, `pt-BR`, …); translate into that language.
28
28
  2. Build your work list:
29
29
 
30
30
  ```bash
@@ -4,7 +4,7 @@
4
4
  Usage:
5
5
  python3 scripts/okstra-inject-report-index.py \\
6
6
  <final-report.md> \\
7
- [--report-language en|ko]
7
+ [--report-language <language-tag>]
8
8
 
9
9
  Adds the top-of-report Index (section list + ID index) and `<a id="...">`
10
10
  scroll anchors to a markdown report that was authored *free-form* rather
@@ -26,7 +26,6 @@ from okstra_bootstrap import prefer_colocated_modules # noqa: E402
26
26
 
27
27
  prefer_colocated_modules(__file__, "okstra_ctl/render_final_report.py")
28
28
 
29
- from okstra_ctl.i18n import SUPPORTED_LANGS # noqa: E402
30
29
  from okstra_ctl.render_final_report import ( # noqa: E402
31
30
  FinalReportRenderError,
32
31
  inject_index_into_file,
@@ -45,9 +44,9 @@ def main(argv: list[str]) -> int:
45
44
  )
46
45
  parser.add_argument(
47
46
  "--report-language",
48
- choices=list(SUPPORTED_LANGS),
49
47
  default="en",
50
- help="Language for the index labels (Index/목차, …). Default: en.",
48
+ help="Language tag for the index labels; a language without its own "
49
+ "dictionary gets the English labels. Default: en.",
51
50
  )
52
51
  args = parser.parse_args(argv)
53
52
 
@@ -8,10 +8,7 @@ Usage:
8
8
  ``extract`` writes the translator's work list — every pointer the report holds
9
9
  a translatable string at, paired with the English text. The translator fills
10
10
  in the values rather than authoring pointers, so a sidecar cannot cite a path
11
- the document does not have. It refuses a data.json that is itself over the
12
- Korean-prose limit: the work list would pair every pointer with target-language
13
- text, and the translator would spend a full pass translating a document into
14
- the language it is already in.
11
+ the document does not have.
15
12
 
16
13
  ``check`` is the translator's own gate before it returns: it resolves every
17
14
  pointer in the sidecar against the report and reports what is still English.
@@ -35,12 +32,7 @@ from okstra_bootstrap import prefer_colocated_modules # noqa: E402
35
32
 
36
33
  prefer_colocated_modules(__file__, "okstra_ctl/report_translation.py")
37
34
 
38
- from okstra_ctl.report_translation import ( # noqa: E402
39
- HANGUL_PROSE_LIMIT,
40
- extract,
41
- hangul_share,
42
- overlay,
43
- )
35
+ from okstra_ctl.report_translation import extract, overlay # noqa: E402
44
36
  from okstra_ctl.final_report_paths import ( # noqa: E402
45
37
  translation_sidecar_path,
46
38
  translation_source_path,
@@ -118,31 +110,9 @@ def _validate_source_payload(
118
110
  )
119
111
 
120
112
 
121
- def _english_source_failure(data_path: Path, data: dict) -> str | None:
122
- """The Korean-prose message for *data*, or None when it reads as English."""
123
- share, _ = hangul_share(data)
124
- if share < HANGUL_PROSE_LIMIT:
125
- return None
126
- return (
127
- f"error: {data_path.name} was authored in Korean "
128
- f"({share:.0%} of its prose, limit {HANGUL_PROSE_LIMIT:.0%}). "
129
- "The data.json is the English SSOT every later phase reads; the "
130
- "report language selects the human HTML's language and is served "
131
- "by the Phase 7 translator, not by authoring the SSOT in it.\n"
132
- )
133
-
134
-
135
113
  def cmd_extract(args: argparse.Namespace) -> int:
136
114
  data_path = Path(args.data).resolve()
137
115
  data = _load(data_path)
138
- # Refuse before building the work list. Extracting from a Korean SSOT
139
- # produces a translation from the target language into itself: a
140
- # full-cost artifact whose English column is not English, discovered
141
- # only later when `check-source` fails the finalize step.
142
- failure = _english_source_failure(data_path, data)
143
- if failure is not None:
144
- sys.stderr.write(failure)
145
- return 1
146
116
  strings = extract(data)
147
117
  out_path = translation_source_path(data_path)
148
118
  lang = str((data.get("meta") or {}).get("reportLanguage") or "")
@@ -171,10 +141,6 @@ def cmd_source(args: argparse.Namespace) -> int:
171
141
  authority, data_path = _translation_authority(Path(args.run_manifest))
172
142
  snapshot = _report_snapshot(data_path)
173
143
  data = snapshot.value
174
- failure = _english_source_failure(data_path, data)
175
- if failure is not None:
176
- sys.stderr.write(failure)
177
- return 1
178
144
  payload = _source_payload(authority, data_path, snapshot.raw_bytes, data)
179
145
  out_path = translation_source_path(data_path)
180
146
  if out_path.is_symlink():
@@ -352,24 +318,6 @@ def _check_sidecar(sidecar_file: Path) -> tuple[dict, int]:
352
318
  return payload, 0
353
319
 
354
320
 
355
- def cmd_check_source(args: argparse.Namespace) -> int:
356
- data_path = Path(args.data).resolve()
357
- data = _load(data_path)
358
- share, length = hangul_share(data)
359
- failure = _english_source_failure(data_path, data)
360
- payload = {
361
- "ok": failure is None,
362
- "hangulShare": round(share, 4),
363
- "limit": HANGUL_PROSE_LIMIT,
364
- "proseChars": length,
365
- }
366
- print(json.dumps(payload, ensure_ascii=False))
367
- if failure is not None:
368
- sys.stderr.write(failure)
369
- return 1
370
- return 0
371
-
372
-
373
321
  _CLI_EPILOG = r"""A thin spawn shim over `scripts/okstra-report-translate.py` (installed at
374
322
  `$HOME/.okstra/bin/okstra-report-translate.py`).
375
323
 
@@ -424,12 +372,6 @@ def _parser() -> argparse.ArgumentParser:
424
372
  check_cmd = sub.add_parser("check", help="verify a filled sidecar against its report")
425
373
  check_cmd.add_argument("sidecar", help="path to final-report-<type>-<seq>.i18n.<lang>.json")
426
374
  check_cmd.set_defaults(func=cmd_check)
427
-
428
- source_cmd = sub.add_parser(
429
- "check-source", help="verify the data.json itself was authored in English"
430
- )
431
- source_cmd.add_argument("data", help="path to final-report-<type>-<seq>.data.json")
432
- source_cmd.set_defaults(func=cmd_check_source)
433
375
  return parser
434
376
 
435
377
 
@@ -464,8 +464,8 @@ The detailed persistence sequence lives in [report-writer](./report-writer.md).
464
464
 
465
465
  Order of operations:
466
466
 
467
- 1. Run `okstra report-finalize ...`. Contract v3 collects usage, assembles the role-owned inputs into `data.json` once, checks the source, renders views, persists follow-ups, validates the run, records this run's conclusion into the task-group's `group-context.md` for the group's sibling tasks, and performs eligible teardown in order. When the task's pointer is terminal and the group has a task not yet started, the result's `nextInGroup` names it and `nextCommand` closes on starting it from its brief — the group's start order is the brief ordinal; the briefs' Related Task Graph edges are quoted as `waits for` and do not reorder it.
468
- 2. When `meta.reportLanguage` is not `en`, the same call dispatches the translator: its `translate` step (after `check-source`, before `render-views`) materializes the translator prompt when this run has no undispatched reservation, runs the CLI-wrapper dispatch, and requires the `*.i18n.<lang>.json` sidecar. Do not dispatch the translator by hand or split the sequence around it; if the step fails, clear the cause and resume with the `--only translate --only render-views …` hint the result prints (**Enforced:** `okstra_ctl.report_translation_dispatch`).
467
+ 1. Run `okstra report-finalize ...`. Contract v3 collects usage, assembles the role-owned inputs into `data.json` once, renders views, persists follow-ups, validates the run, records this run's conclusion into the task-group's `group-context.md` for the group's sibling tasks, and performs eligible teardown in order. When the task's pointer is terminal and the group has a task not yet started, the result's `nextInGroup` names it and `nextCommand` closes on starting it from its brief — the group's start order is the brief ordinal; the briefs' Related Task Graph edges are quoted as `waits for` and do not reorder it.
468
+ 2. When `meta.reportLanguage` is not `en`, the same call dispatches the translator: its `translate` step (directly before `render-views`) materializes the translator prompt when this run has no undispatched reservation, runs the CLI-wrapper dispatch, and requires the `*.i18n.<lang>.json` sidecar. Do not dispatch the translator by hand or split the sequence around it; if the step fails, clear the cause and resume with the `--only translate --only render-views …` hint the result prints (**Enforced:** `okstra_ctl.report_translation_dispatch`).
469
469
 
470
470
  Keep the assigned worker prompt history paths stable in `team-state`, `run-manifest`, and `task-manifest`. Do not rewrite prompt artifacts to `/tmp` or omit prompt metadata for attempted workers.
471
471
 
@@ -427,7 +427,7 @@ round before any host or provider process starts.
427
427
  - **Drop plan items whose element the round deleted.** A self-fix rewrite may remove a plan element (a validation check, a rollback row). `P-*` ids are positional, so a deletion shifts every later row and silently re-points surviving verdicts at their neighbours — and a verdict recorded against a removed element keeps blocking a gate while being unfindable in the plan, so reading the plan never reveals the cause. After each round, re-extract plan items with `okstra plan-items extract` and re-verify any item whose `subject` no longer matches; never carry the old vote forward across a shift. **Enforced:** `validators/validate-run.py` `_validate_verdicts_match_current_subjects` (re-pointing) and `_validate_plan_item_extraction_completeness` (dangling ids).
428
428
  - **Classify each cause group before instructing it (BLOCKING).** A group is either an *authoring* defect — the plan says something wrong, incomplete, or self-contradictory, which self-fix owns — or a *citation* defect, where the plan points at an analysis artifact incorrectly. Only the first is self-fix work. For the second the finding already exists and already went through convergence, so the fix is to re-cite the converged artifact; instructing report-writer to re-derive the fact means the author reads the source material and produces a **finding that never went through convergence**, which the plan then carries as if it had. That is the role boundary the lead contract draws ("keep analysis, execution, verification, and report authoring responsibilities distinct; return defects to the role that owns them"), and report-writer is authoring-only by its own contract. `P-Req-*` items with breakage kind `f` are where this goes wrong most often: the question is usually whether a coverage row points correctly at something already measured, not whether the measurement is right. State the classification in the group's instruction so the author knows which of the two it is being asked to do.
429
429
  - **A verdict older than the last self-fix is not a verdict unless the item's content is unchanged (BLOCKING).** A verdict cast in round 1 judged the text before the only automatic rewrite. Once that rewrite runs, a changed item's judgement is about a plan that no longer exists. `--round <N>` on `apply-verdicts` stamps each row and copies `contentHash` onto `verifiedContentHash`. `validators/validate-run.py` `_validate_verdict_rounds_outlive_self_fix` fails an in-scope item whose verdict round is at or before `selfFixRoundsApplied` **and** whose `contentHash` does not match `verifiedContentHash`. Matching hashes keep the prior verdict — that is what avoids a sweep round over unchanged stages. Deferred and observed items are out of the gate and do not need a post-self-fix verdict. **Enforced:** `_validate_verdict_rounds_outlive_self_fix`.
430
- - Lead re-runs plan-body verification, then records each worker Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker>=<result.md> --round <N>`. Score the result with `okstra plan-verify --narrative <report-writer-narrative.md> --state <plan-body-verification.json>`, then call `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. These commands fail on an assigned item the worker left unanswered, on a verdict for an item outside the queue, and on a duplicate worker result. `apply-verdicts` without `--append` replaces every recorded row of the queued items, so it also refuses — before writing — when a row belongs to a round that `complete-round` never closed, naming the round to close first; the votes of a closed round live in `planItems[].rounds`. Skipping `complete-round` between rounds and applying the next one lost 19 items' round-1 votes (2026-09-09). `--discard-open-rounds` is only for re-applying the lost rounds from their result files in order. **Enforced:** `okstra_ctl.plan_items_cli._reject_uncompleted_round_loss`.
430
+ - Lead re-runs plan-body verification, then records each worker Markdown result through `okstra plan-items apply-verdicts --state <plan-body-verification.json> --result <worker>=<result.md> --round <N>`. Score the result with `okstra plan-verify --narrative <report-writer-narrative.md> --state <plan-body-verification.json>`, then call `okstra plan-items complete-round --state <plan-body-verification.json> --run-manifest <current-run-manifest.json> --round <N>`. These commands fail on an assigned item the worker left unanswered, on a verdict for an item outside the queue, and on a duplicate worker result. `apply-verdicts` without `--append` replaces every recorded row of the queued items, so it also refuses — before writing — when a row belongs to a round that `complete-round` never closed, naming the round to close first; the votes of a closed round live in `planItems[].rounds`. Skipping `complete-round` between rounds and applying the next one lost 19 items' round-1 votes (2026-09-09). `--discard-open-rounds` is only for re-applying the lost rounds from their result files in order: it also restores `dispatchQueue` to the items those result files answer, because the persisted queue is the latest round's and a verdict for an earlier, wider queue is otherwise refused as outside it (round 1 = 26 items, critic tie = 8, round 3 = 19 on that same run — only the 19 survived). Re-apply each lost round with the flag, `complete-round` it, then the next. **Enforced:** `okstra_ctl.plan_items_cli._reject_uncompleted_round_loss` and `_restore_queue_from_results`.
431
431
  - For a self-fix, record the correction through the typed convergence command rather than writing `selfFixNote` or `selfFixGroups` JSON. A resolved item does not create a clarification.
432
432
  - **Each round is a worker batch.** Before dispatching round N ≥ 2, reclaim the previous round's completed verifiers exactly as at any other batch boundary ([okstra-lead-contract](./okstra-lead-contract.md) "Run-scoped worker-resource lifecycle") and emit `PROGRESS: phase-batch-cleanup panes=<n>`, then announce the round with `PROGRESS: phase-5.5.9-plan-verify round=<N> items=<count>`. Saying a round will "reuse" the previous verifiers and then dispatching under fresh names leaves every prior round holding its panes — five rounds of that is what exhausts the pane budget and blocks the next dispatch. **Enforced:** `validators/validate_session_conformance.py` `_check_plan_verify_cleanup_checkpoints` requires both lines once the state file records two or more rounds.
433
433
  - **Round completion.** A round is complete only after `okstra plan-verify` exits 0 and `okstra plan-items complete-round` succeeds. A round left with a non-zero exit carries its defect into the next round's inputs. Exit 0 with a non-empty `advisories[]` is a complete round: those findings are recorded, not round-blocking (step 5, §"`failures[]` carries only the blocking findings"). Report assembly and rendering occur only after the convergence state is terminal. **Enforced:** `validators/validate-run.py` `_validate_plan_body_state_rounds` requires one stored round per round number and a corresponding item vote.
@@ -123,23 +123,22 @@ For historical schema-v1 Markdown only, the following heading table remains a re
123
123
 
124
124
  **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` is the order — `report-finalize` runs the steps from that tuple, so the sequence cannot be reordered by a caller. Running the steps by hand is what this rule forbids, and that path is not reachable through the CLI.
125
125
 
126
- Do not run the nine steps below manually. Invoke `okstra report-finalize`; contract 3.0 runs them in this order:
126
+ Do not run the eight steps below manually. Invoke `okstra report-finalize`; contract 3.0 runs them in this order:
127
127
 
128
128
  1. **`token-usage`** — collect usage into team state without touching the final record.
129
129
  2. **`project-activity`** — report assembly validates every owner input and publishes the final record once.
130
- 3. **`check-source`** — verify the assembled English source.
131
- 4. **`translate`** — for a non-English `reportLanguage`, materialize and dispatch the translator worker and require its `*.i18n.<lang>.json` sidecar; a no-op for English or when the sidecar already exists.
132
- 5. **`render-views`** — render the Markdown reading copy and human HTML, with the translation sidecar overlaid.
133
- 6. **`spawn-followups`** — materialize registered follow-up tasks.
134
- 7. **`validate-run`** — validate the record, views, run manifest, and team state.
135
- 8. **`record-group-memory`** — write this run's conclusion (headline, decisions, watch-outs, open follow-ups, record path, next phase) and the group's start order into the task-group's `group-context.md` okstra region, creating the file when the group has none; skipped, like teardown, when an earlier step failed. Sibling tasks read it as `## Task-Group Memory`.
136
- 9. **`teardown-stages`** — remove eligible stage worktrees after successful validation.
130
+ 3. **`translate`** — for a non-English `reportLanguage`, materialize and dispatch the translator worker and require its `*.i18n.<lang>.json` sidecar; a no-op for English or when the sidecar already exists.
131
+ 4. **`render-views`** — render the Markdown reading copy and human HTML, with the translation sidecar overlaid.
132
+ 5. **`spawn-followups`** — materialize registered follow-up tasks.
133
+ 6. **`validate-run`** — validate the record, views, run manifest, and team state.
134
+ 7. **`record-group-memory`** — write this run's conclusion (headline, decisions, watch-outs, open follow-ups, record path, next phase) and the group's start order into the task-group's `group-context.md` okstra region, creating the file when the group has none; skipped, like teardown, when an earlier step failed. Sibling tasks read it as `## Task-Group Memory`.
135
+ 8. **`teardown-stages`** — remove eligible stage worktrees after successful validation.
137
136
 
138
137
  After `report-finalize` returns, the lead — not the report writer — closes the run with the launch prompt's User closeout: one command the user can run now.
139
138
 
140
139
  ### The translation sidecar: the `translate` step
141
140
 
142
- `report-finalize` dispatches the translator itself. Its `translate` step runs after `check-source` (the extractor refuses a work list from a non-English source) and before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then runs the CLI-wrapper dispatch, which records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
141
+ `report-finalize` dispatches the translator itself. Its `translate` step runs directly before `render-views` (which overlays the sidecar), and it is a no-op when `reportLanguage` is `en` or the `*.i18n.<lang>.json` sidecar already exists. When the sidecar is missing, the step reuses this run's undispatched translator reservation if the lead already materialized one, otherwise it materializes the prompt itself — instruction file `state/translator-instructions-<task-type>-<seq>.md` (kept when the lead wrote one), prompt `prompts/translator-worker-prompt-<task-type>-<seq>.md`, `--result worker-results/translator-translations-<task-type>-<seq>.md`, `--audit-source worker-results/translator-worker-<task-type>-<seq>.md`, invocation id `<task-type>-<seq>-translator` (`-r2`, `-r3` after a failed attempt) — then runs the CLI-wrapper dispatch, which records the dispatch and links the result. Do not run `agent-prompt materialize --audience translator` or `worker-dispatch --workers translator` by hand before `report-finalize`; the sequence used to be a manual lead step and was skipped in practice (2026-09-09, fontsninja-v3-site dev-10628-3: a `ko` run finalized in one call, zero translator reservations, English HTML). **Enforced:** `okstra_ctl.report_finalize.V3_STEP_ORDER` places the step; `okstra_ctl.report_translation_dispatch.translate_report` owns it; `_translator_job_from_reservation` in `scripts/okstra_ctl/dispatch_core.py` still refuses two undispatched reservations for one run, which only a hand-made second reservation produces.
143
142
 
144
143
  The step succeeds only when the sidecar exists afterwards; a translator that exits 0 without publishing it fails the step. A failed `translate` does not stop the sequence: `render-views` still writes the view from the English body, `validate-run` records the missing sidecar as an advisory, and the result's resume hint starts at `--only translate --only render-views …` — run that once the cause (a host approval gate, an unavailable provider) is cleared rather than closing the run on an English view.
145
144
 
@@ -13,7 +13,7 @@ from typing import Any, Callable
13
13
 
14
14
  from .json_boundary import JsonBoundaryError, load_owned_object
15
15
 
16
- SUPPORTED_LANGS = ("en", "ko")
16
+ FALLBACK_LANG = "en"
17
17
  DICTIONARY_REL = ("templates", "reports", "i18n")
18
18
  # The human HTML has its own fixed strings — table headings, empty states,
19
19
  # enum labels — that the AI-handoff Markdown never renders. Same loader, its
@@ -44,11 +44,16 @@ def _i18n_dir(rel: tuple[str, ...] = DICTIONARY_REL) -> Path:
44
44
 
45
45
 
46
46
  def load_dictionary(lang: str, rel: tuple[str, ...] = DICTIONARY_REL) -> dict[str, Any]:
47
- if lang not in SUPPORTED_LANGS:
48
- raise I18nError(
49
- f"unsupported reportLanguage {lang!r}; supported: {SUPPORTED_LANGS}"
50
- )
51
- path = _i18n_dir(rel) / f"{lang}.json"
47
+ """`<lang>.json` 있으면 그것, 없으면 영어 사전.
48
+
49
+ 사전은 페이지 크롬(표 머리글·빈 상태·enum 라벨)의 고정 문자열이고 본문은
50
+ 번역 사이드카가 덮는다. 사전이 없는 언어의 리포트는 본문은 그 언어, 크롬은
51
+ 영어로 나온다 사전이 없다고 렌더를 거절하면 독자는 아무것도 못 받는다.
52
+ """
53
+ directory = _i18n_dir(rel)
54
+ path = directory / f"{lang}.json"
55
+ if not path.is_file():
56
+ path = directory / f"{FALLBACK_LANG}.json"
52
57
  try:
53
58
  return load_owned_object(path, artifact="i18n dictionary")
54
59
  except JsonBoundaryError as exc:
@@ -263,7 +263,9 @@ def _parser() -> argparse.ArgumentParser:
263
263
  "--discard-open-rounds", action="store_true",
264
264
  help="replace even the rows of a round that complete-round never "
265
265
  "closed — the recovery path when those rows are being re-applied "
266
- "from their result files in order; the discarded rows are listed",
266
+ "from their result files in order; the discarded rows are listed, "
267
+ "and dispatchQueue is restored to the items those result files "
268
+ "answer, since the persisted queue belongs to the latest round",
267
269
  )
268
270
  complete = commands.add_parser(
269
271
  "complete-round",
@@ -1367,6 +1369,9 @@ def _apply_verdicts(args: argparse.Namespace) -> dict[str, Any]:
1367
1369
  data["planBodyVerification"] if args.state is not None
1368
1370
  else _planning(data)["planBodyVerification"]
1369
1371
  )
1372
+ discard_open_rounds = bool(getattr(args, "discard_open_rounds", False))
1373
+ if discard_open_rounds and args.state is not None and args.result:
1374
+ _restore_queue_from_results(verification, args.result, recorded)
1370
1375
  queue = verification.get("dispatchQueue") if isinstance(verification, Mapping) else None
1371
1376
  assigned = (
1372
1377
  {item_id for item_id in queue if isinstance(item_id, str)}
@@ -1399,7 +1404,7 @@ def _apply_verdicts(args: argparse.Namespace) -> dict[str, Any]:
1399
1404
  if not append:
1400
1405
  _reject_uncompleted_round_loss(
1401
1406
  recorded, rows, data.get("roundHistory"), args.round_number, target,
1402
- discard_open_rounds=bool(getattr(args, "discard_open_rounds", False)),
1407
+ discard_open_rounds=discard_open_rounds,
1403
1408
  )
1404
1409
  writer = _append_item_verdicts if append else _replace_item_verdicts
1405
1410
  for item in recorded:
@@ -1459,6 +1464,47 @@ def _append_item_verdicts(
1459
1464
  _remember_verified_hash(item)
1460
1465
 
1461
1466
 
1467
+ def _restore_queue_from_results(
1468
+ verification: Any,
1469
+ raw_results: list[str],
1470
+ recorded: Sequence[Mapping[str, Any]],
1471
+ ) -> None:
1472
+ """재적용하는 라운드의 큐를 그 라운드의 결과 파일이 답한 항목 집합으로 되돌린다.
1473
+
1474
+ `dispatchQueue` 는 값이 하나뿐이라 라운드마다 덮인다 — 라운드 1 이 26개,
1475
+ critic 동수 라운드가 8개, 라운드 3 이 19개였던 run 에서 상태에 남는 것은
1476
+ 19개뿐이다. `apply-verdicts` 와 `complete-round` 는 둘 다 그 큐로 스코프하고,
1477
+ `_queue_for` 는 이미 두 번 전진한 해시로부터의 순수 계산이라 지난 큐를
1478
+ 되살릴 명령이 없었다. 그래서 `--discard-open-rounds` 로 라운드 1 을 다시
1479
+ 적용하면 큐 밖 id 라며 거절됐고, 복구는 상태 파일을 손으로 고쳐야만 됐다
1480
+ (실측 2026-09-09, `fontsninja-v3-site` dev-10627 planning 002).
1481
+
1482
+ 결과 파일의 `### P-*` 블록 집합이 곧 그 라운드의 큐다 — 워커는 배정된 항목
1483
+ 전부에 답해야 통과하고(`_worker_blocks`), 큐 밖 항목에는 답할 수 없었다.
1484
+ 순서는 계획 항목 순서를 따른다. 계획에 없는 id 는 큐에 넣지 않으므로 뒤의
1485
+ `_worker_blocks` 가 그 id 를 이름하며 거절한다.
1486
+ """
1487
+ if not isinstance(verification, dict):
1488
+ return
1489
+ answered: set[str] = set()
1490
+ for raw in raw_results:
1491
+ _worker, path = _split_result_arg(raw)
1492
+ try:
1493
+ answered |= set(parse_verdict_blocks(path.read_text(encoding="utf-8")))
1494
+ except (OSError, UnicodeError) as exc:
1495
+ raise PlanItemContractError(f"cannot read result {path}: {exc}") from exc
1496
+ restored = [
1497
+ str(item["id"]) for item in recorded
1498
+ if isinstance(item, Mapping) and item.get("id") in answered
1499
+ ]
1500
+ verification["dispatchQueue"] = restored
1501
+ print(
1502
+ f"apply-verdicts: dispatchQueue restored from the result files "
1503
+ f"({len(restored)} items)",
1504
+ file=sys.stderr,
1505
+ )
1506
+
1507
+
1462
1508
  def _reject_uncompleted_round_loss(
1463
1509
  recorded: Sequence[Mapping[str, Any]],
1464
1510
  rows: Mapping[str, Any],
@@ -51,7 +51,8 @@ from okstra_ctl.final_report_schema import (
51
51
  validate as schema_validate,
52
52
  )
53
53
  from okstra_ctl.json_boundary import JsonBoundaryError, load_owned_object
54
- from okstra_ctl.i18n import I18nError, SUPPORTED_LANGS, load_dictionary, make_jinja_global
54
+ from okstra_ctl.i18n import I18nError, load_dictionary, make_jinja_global
55
+ from okstra_ctl.report_language import is_language_tag
55
56
  from okstra_ctl.md_table import UNESCAPED_PIPE_RE, to_cell_text
56
57
  from okstra_ctl.models import UnknownModelError, resolve_model_metadata
57
58
  from okstra_ctl.paths import find_asset_root
@@ -598,15 +599,10 @@ def validate_report_language(data: dict) -> str:
598
599
  selects this renderer's dictionary.
599
600
  """
600
601
  candidate = (data.get("meta") or {}).get("reportLanguage") or "en"
601
- if candidate == "auto":
602
+ if not is_language_tag(candidate):
602
603
  raise FinalReportRenderError(
603
- "reportLanguage 'auto' must be resolved by the lead before "
604
- "the renderer runs; report assembly copies the resolved 'en' or "
605
- "'ko' from the run manifest into data.json.meta.reportLanguage."
606
- )
607
- if candidate not in SUPPORTED_LANGS:
608
- raise FinalReportRenderError(
609
- f"reportLanguage must be one of {SUPPORTED_LANGS}, got {candidate!r}"
604
+ f"reportLanguage must be a language tag such as en, ko, fr or "
605
+ f"pt-BR, got {candidate!r}"
610
606
  )
611
607
  return MARKDOWN_LANG
612
608
 
@@ -25,10 +25,8 @@ placeholders, which a v2 report never carries because its numeric cells are
25
25
  `null` until this step fills them. Substituting the tokens on a later retry
26
26
  then leaves the already-rendered html stale for `validators/validate-report-views.py`.
27
27
 
28
- The translation sidecar is the `translate` step, between `check-source` and
29
- `render-views`: `render-views` overlays it, and `check-source` has to pass first
30
- because the extractor refuses a work list from a non-English source. It used to
31
- be a manual lead sequence outside this command (materialize the translator
28
+ The translation sidecar is the `translate` step, directly before `render-views`,
29
+ which overlays it. It used to be a manual lead sequence outside this command (materialize the translator
32
30
  prompt, dispatch it, resume finalize at `render-views`), written only in a doc
33
31
  the lead lazy-reads; a lead that ran the whole sequence at once skipped it and
34
32
  the run kept an English view under a `ko` report (2026-09-09, fontsninja-v3-site
@@ -88,7 +86,6 @@ from okstra_project.phase_pointer import (
88
86
 
89
87
 
90
88
  STEP_PROJECT_ACTIVITY = "project-activity"
91
- STEP_CHECK_SOURCE = "check-source"
92
89
  STEP_TRANSLATE = "translate"
93
90
  STEP_TOKEN_USAGE = "token-usage"
94
91
  STEP_RENDER_VIEWS = "render-views"
@@ -99,10 +96,6 @@ STEP_TEARDOWN_STAGES = "teardown-stages"
99
96
 
100
97
  STEP_ORDER = (
101
98
  STEP_PROJECT_ACTIVITY,
102
- # First, because everything after it derives from the data.json: rendering
103
- # a Korean SSOT into English chrome, spawning follow-ups from it, and
104
- # validating it all succeed on a record the next phase cannot read.
105
- STEP_CHECK_SOURCE,
106
99
  STEP_TRANSLATE,
107
100
  STEP_TOKEN_USAGE,
108
101
  STEP_RENDER_VIEWS,
@@ -119,8 +112,7 @@ STEP_ORDER = (
119
112
  V3_STEP_ORDER = (
120
113
  STEP_TOKEN_USAGE,
121
114
  STEP_PROJECT_ACTIVITY,
122
- STEP_CHECK_SOURCE,
123
- # After the English gate, before the render that overlays its sidecar.
115
+ # Before the render that overlays its sidecar.
124
116
  STEP_TRANSLATE,
125
117
  STEP_RENDER_VIEWS,
126
118
  STEP_SPAWN_FOLLOWUPS,
@@ -334,19 +326,6 @@ def build_commands(ctx: FinalizeContext) -> list[tuple[str, list[str]]]:
334
326
  str(ctx.data_path),
335
327
  ],
336
328
  ),
337
- (
338
- STEP_CHECK_SOURCE,
339
- [
340
- sys.executable,
341
- str(
342
- resolve_workspace_script(
343
- ctx.workspace_root, "okstra-report-translate.py"
344
- )
345
- ),
346
- "check-source",
347
- str(ctx.data_path),
348
- ],
349
- ),
350
329
  (
351
330
  STEP_TRANSLATE,
352
331
  ["<in-process>", "translate", str(ctx.data_path)],
@@ -976,18 +955,17 @@ _CLI_EPILOG = r"""Usage:
976
955
  Runs the Phase 7 steps in their contractual order against one final-report:
977
956
 
978
957
  1. project-activity project the run's activity events into the data.json
979
- 2. check-source verify the data.json is the English SSOT
980
- 3. translate for a non-English reportLanguage, materialize and
958
+ 2. translate for a non-English reportLanguage, materialize and
981
959
  dispatch the translator worker and require its
982
960
  *.i18n.<lang>.json sidecar; a no-op for English or
983
961
  when the sidecar already exists
984
- 4. token-usage substitute real token/cost numbers into the data.json
985
- 5. render-views write the schema-v2 task-specific *.html sibling with
962
+ 3. token-usage substitute real token/cost numbers into the data.json
963
+ 4. render-views write the schema-v2 task-specific *.html sibling with
986
964
  the translation overlaid; v1 keeps the
987
965
  legacy conditional interactive view
988
- 6. spawn-followups turn section 4 rows into task stubs
989
- 7. validate-run validate the finished run artifacts
990
- 8. record-group-memory / 9. teardown-stages after a clean validation
966
+ 5. spawn-followups turn section 4 rows into task stubs
967
+ 6. validate-run validate the finished run artifacts
968
+ 7. record-group-memory / 8. teardown-stages after a clean validation
991
969
 
992
970
  Every step is idempotent, so re-running after a fixed failure is safe. The
993
971
  sequence stops at the first non-zero exit and reports which step failed, except
@@ -12,7 +12,7 @@ from jinja2 import Environment, FileSystemLoader, StrictUndefined, select_autoes
12
12
  from ..final_report_paths import final_report_data_path, translation_sidecar_path
13
13
  from ..final_report_schema import load_schema_for_data, validate
14
14
  from ..i18n import HTML_DICTIONARY_REL, load_dictionary, make_jinja_global
15
- from ..report_translation import overlay, rendered_english_lines
15
+ from ..report_translation import overlay
16
16
  from ..report_view_artifacts import user_responses_dir_for_report
17
17
  from ..usage_cells import format_duration_ms
18
18
  from ..json_boundary import load_owned_object
@@ -223,16 +223,6 @@ def render_v2_html_view(
223
223
  document = env.get_template(route.template_name).render(**context)
224
224
  document = inject_report_index(document, label=translate("base.contents"))
225
225
  output_path.write_text(document, encoding="utf-8")
226
- if lang != "en":
227
- # 사이드카 보고("left in English")는 추출 범위 안만 센다. 페이지에 실제로
228
- # 남은 영어 문장은 여기서 세어 같은 stdout 에 낸다 — 두 숫자가 갈리면
229
- # `PROSE_KEYS` 밖의 필드가 화면에 나가고 있다는 뜻이다.
230
- english = rendered_english_lines(document)
231
- sys.stdout.write(
232
- f"rendered {len(english)} line(s) still in English on the {lang} page"
233
- + (f" (e.g. {english[0][:80]!r})" if english else "")
234
- + "\n"
235
- )
236
226
  if context["clarificationItems"] or context.get("directionSelection"):
237
227
  # The footer tells the reader to drop the exported file here, so the
238
228
  # directory has to exist before they go looking for it.
@@ -6,28 +6,32 @@ the run manifest, which report assembly copies into `meta.reportLanguage` and
6
6
  the HTML renderer reads. When only the prompt called this, a project could ask
7
7
  for `ko`, get a Korean-instructed writer, and still ship an English report.
8
8
 
9
- Precedence: project config global config inferred from the task brief.
9
+ The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The Phase 7
10
+ translator receives that tag and translates into it; nothing here knows any
11
+ particular language. Precedence: project config → global config → `en`.
10
12
  """
11
13
  from __future__ import annotations
12
14
 
13
- import json
14
15
  import os
16
+ import re
15
17
  from pathlib import Path
16
18
  from typing import Any, Mapping
17
19
 
18
- from .worker_prompt_body import instruction_path
19
20
  from .json_boundary import JsonBoundaryError, load_owned_object
20
21
 
21
- REPORT_LANGUAGE_VALUES = {"en", "ko", "auto"}
22
+ DEFAULT_REPORT_LANGUAGE = "en"
22
23
 
23
- # Below this many Hangul syllables a brief reads as incidental Korean in an
24
- # otherwise English document.
25
- _HANGUL_THRESHOLD = 10
26
- _BRIEF_SCAN_CHARS = 16000
24
+ # BCP 47 흔한 꼴만 소문자 2~3자 언어 코드와 선택적 하위 태그. 대문자
25
+ # 언어 코드나 단어(`english`)는 사이드카 파일명과 사전 파일명이 갈리므로 거절.
26
+ _LANGUAGE_TAG = re.compile(r"^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$")
27
27
 
28
28
 
29
29
  class ReportLanguageError(ValueError):
30
- """A config file declares a reportLanguage outside the allowed values."""
30
+ """A config file declares a reportLanguage that is not a language tag."""
31
+
32
+
33
+ def is_language_tag(value: object) -> bool:
34
+ return isinstance(value, str) and _LANGUAGE_TAG.match(value) is not None
31
35
 
32
36
 
33
37
  def resolve_report_language(
@@ -36,11 +40,9 @@ def resolve_report_language(
36
40
  active_context: Mapping[str, Any],
37
41
  ) -> str:
38
42
  for value in _report_language_candidates(project_root):
39
- if value == "auto":
40
- return _infer_report_language(project_root, manifest, active_context)
41
43
  if value:
42
44
  return value
43
- return _infer_report_language(project_root, manifest, active_context)
45
+ return DEFAULT_REPORT_LANGUAGE
44
46
 
45
47
 
46
48
  def _report_language_candidates(project_root: Path) -> list[str]:
@@ -63,26 +65,9 @@ def _read_report_language(path: Path, label: str) -> str:
63
65
  value = payload.get("reportLanguage")
64
66
  if value in (None, ""):
65
67
  return ""
66
- if isinstance(value, str) and value in REPORT_LANGUAGE_VALUES:
68
+ if is_language_tag(value):
67
69
  return value
68
70
  raise ReportLanguageError(
69
- f"{label} config reportLanguage must be en, ko, or auto: {path}"
71
+ f"{label} config reportLanguage must be a language tag such as en, ko, "
72
+ f"fr or pt-BR, got {value!r}: {path}"
70
73
  )
71
-
72
-
73
- def _infer_report_language(
74
- project_root: Path,
75
- manifest: Mapping[str, Any],
76
- active_context: Mapping[str, Any],
77
- ) -> str:
78
- task_brief = instruction_path(manifest, active_context, "taskBriefPath")
79
- if not task_brief:
80
- return "en"
81
- path = Path(task_brief)
82
- if not path.is_absolute():
83
- path = project_root / path
84
- if not path.is_file():
85
- return "en"
86
- text = path.read_text(encoding="utf-8", errors="replace")[:_BRIEF_SCAN_CHARS]
87
- hangul = sum(1 for char in text if "가" <= char <= "힣")
88
- return "ko" if hangul >= _HANGUL_THRESHOLD else "en"
@@ -441,57 +441,6 @@ def overlay(
441
441
  return out, OverlayReport(applied, untranslated, tuple(sorted(unresolved)))
442
442
 
443
443
 
444
- # Above this share of Hangul, the prose was authored in Korean rather than
445
- # quoting some. Measured across real reports: Korean-authored ones sit at
446
- # 35-38%, English ones that quote a Korean brief or a worker's Korean phrase
447
- # reach 8% at most. The gap is wide enough that no threshold inside it is
448
- # delicate. Only the strings `extract` offers are counted — a verbatim quote of
449
- # the user's request is structural and never reaches this.
450
- HANGUL_PROSE_LIMIT = 0.20
451
-
452
-
453
- _RENDERED_SKIP_RE = re.compile(
454
- r"<script.*?</script>|<style.*?</style>|<svg.*?</svg>|<pre.*?</pre>|<option.*?</option>",
455
- re.DOTALL,
456
- )
457
- _TEXT_NODE_RE = re.compile(r">([^<>]{40,})<")
458
- _ENTITY_RE = re.compile(r"&[#A-Za-z0-9]+;")
459
-
460
-
461
- def rendered_english_lines(html: str, *, min_words: int = 6) -> list[str]:
462
- """Text nodes of a rendered page that carry no Hangul and read as a sentence.
463
-
464
- `OverlayReport.untranslated` only sees the pointers the extractor offered,
465
- so a reader-facing field outside `PROSE_KEYS` never shows up there — the
466
- 2026-09-05 audit found 40 plan-item explanations and 29 step actions in
467
- English on a page that reported "0 left in English". This reads the page
468
- itself. Scripts, styles, figures, code blocks and `<option>` values are
469
- skipped: figure labels and response values are meant to stay verbatim.
470
- Inline `<code>` is stripped so a sentence around a path still counts.
471
- """
472
- body = html.split("<main", 1)[-1]
473
- body = _RENDERED_SKIP_RE.sub(" ", body)
474
- body = re.sub(r"</?code>", "", body)
475
- lines: list[str] = []
476
- for match in _TEXT_NODE_RE.finditer(body):
477
- text = _ENTITY_RE.sub(" ", match.group(1)).strip()
478
- if len(text.split()) < min_words:
479
- continue
480
- if any("가" <= char <= "힣" for char in text):
481
- continue
482
- lines.append(text)
483
- return lines
484
-
485
-
486
- def hangul_share(data: Mapping[str, Any]) -> tuple[float, int]:
487
- """Return the Hangul share of the report's authored prose, and its length."""
488
- text = "".join(extract(data).values())
489
- if not text:
490
- return 0.0, 0
491
- hangul = sum(1 for char in text if "가" <= char <= "힣")
492
- return hangul / len(text), len(text)
493
-
494
-
495
444
  def _schema_string_keys(schema: Mapping[str, Any], *, enums: bool) -> set[str]:
496
445
  found: set[str] = set()
497
446
 
@@ -37,16 +37,12 @@ from .json_boundary import JsonBoundaryError
37
37
 
38
38
  TRANSLATOR_WORKER_ID = "translator"
39
39
 
40
- # 지시문의 언어 이름. 워커는 `**Report Language:**` 헤더로 코드를 받지만, 지시문은
41
- # 사람이 읽는 문장이라 이름으로 적는다. 표에 없는 코드는 코드 그대로 쓴다.
42
- _LANGUAGE_NAMES = {"ko": "Korean"}
43
-
44
40
  # 리드가 종전에 손으로 쓰던 지시문(실측 2026-09-08, fontsninja-nlpvibe dev-10786
45
41
  # requirements-discovery-002)과 같은 절차다. 명령은 `agents/workers/translator-worker.md`
46
42
  # 의 Procedure 와 같은 세 개이고, 결과·감사 경로는 프롬프트 앵커가 이름한다.
47
43
  _INSTRUCTIONS = """## Instructions
48
44
 
49
- Translate the finalized report into {language} faithfully. This is translation only: no analysis, no source edits.
45
+ Translate the finalized report into the language whose IETF language tag is `{language}` faithfully. This is translation only: no analysis, no source edits.
50
46
 
51
47
  1. Run `okstra report-translate source --run-manifest {manifest}` and read the complete work list, in chunks if it is long. Copy the `Source digest` it prints.
52
48
  2. Write one translated block per `## T-NNN` item to the exact Result Path, using the same `## T-NNN` headings. Preserve every identifier, path, command, enum token, link, and qualification character for character.
@@ -229,7 +225,7 @@ def _materialize_translator(
229
225
  instruction.parent.mkdir(parents=True, exist_ok=True)
230
226
  instruction.write_text(
231
227
  _INSTRUCTIONS.format(
232
- language=_LANGUAGE_NAMES.get(lang, lang),
228
+ language=lang,
233
229
  manifest=manifest_path.relative_to(project_root).as_posix()
234
230
  if manifest_path.is_relative_to(project_root)
235
231
  else str(manifest_path),
@@ -5,12 +5,23 @@
5
5
  """
6
6
  from __future__ import annotations
7
7
 
8
+ import os
9
+ import sys
8
10
  from datetime import datetime
9
11
  from pathlib import Path
10
12
  from typing import Callable
11
13
 
12
14
  OKSTRA = "okstra"
13
15
  _SPEAKER_WIDTH = 14
16
+ _RESET = "\x1b[0m"
17
+ _MUTED = "\x1b[90m"
18
+ _LIVE_COLORS = (
19
+ ("→ ", "\x1b[36m"),
20
+ (" ← ok", "\x1b[32m"),
21
+ (" ← error", "\x1b[31m"),
22
+ ("!! PERMISSION DENIED", "\x1b[1;31m"),
23
+ (" ← done", "\x1b[33m"),
24
+ )
14
25
 
15
26
  # 파일 사본이 담는 워커 진행 줄의 상한. 진행은 워커 출력의 부피가 몰리는
16
27
  # 자리다 — 파일 하나만 읽는 디스패치도 킬로바이트 단위 도구 에코를 남기고,
@@ -39,6 +50,12 @@ class SessionTranscript:
39
50
  path.parent.mkdir(parents=True, exist_ok=True)
40
51
  self._file = path.open("w", encoding="utf-8")
41
52
  self._live = live
53
+ self._color = (
54
+ live
55
+ and sys.stdout.isatty()
56
+ and not os.environ.get("NO_COLOR")
57
+ and os.environ.get("TERM") != "dumb"
58
+ )
42
59
  self._clock = clock
43
60
  self._archived = 0
44
61
  self._elided = 0
@@ -51,7 +68,7 @@ class SessionTranscript:
51
68
  잘린 결론은 사후 분석 전체를 잃는다.
52
69
  """
53
70
  row = self._row(speaker, line)
54
- self._show(row)
71
+ self._show(row, line)
55
72
  self._keep(row, capped=capped)
56
73
 
57
74
  def write_event(
@@ -67,7 +84,7 @@ class SessionTranscript:
67
84
  기록에서는 46KB 를 읽은 호출과 잘린 호출이 같은 줄로 보인다.
68
85
  """
69
86
  for line in screen:
70
- self._show(self._row(speaker, line))
87
+ self._show(self._row(speaker, line), line)
71
88
  for line in archive:
72
89
  self._keep(self._row(speaker, line), capped=True)
73
90
 
@@ -77,9 +94,22 @@ class SessionTranscript:
77
94
  label = f"[{speaker}]".ljust(_SPEAKER_WIDTH)
78
95
  return f"{self._clock()} {label}{line}".rstrip()
79
96
 
80
- def _show(self, row: str) -> None:
97
+ def _show(self, row: str, line: str) -> None:
81
98
  # 화면은 상한과 무관하다. 사람이 보고 있는 것을 잘라 낼 이유가 없다.
82
99
  if self._live:
100
+ if self._color:
101
+ color = next(
102
+ (color for prefix, color in _LIVE_COLORS if line.startswith(prefix)),
103
+ "",
104
+ )
105
+ if line.lstrip().startswith("… +"):
106
+ color = "\x1b[33m"
107
+ # 시각·화자는 낮은 대비로 두고 본문은 터미널 기본색을 유지한다.
108
+ prefix_size = len(row) - len(line.rstrip())
109
+ row = (
110
+ f"{_MUTED}{row[:prefix_size]}{_RESET}"
111
+ f"{color}{row[prefix_size:]}{_RESET}"
112
+ )
83
113
  print(row, flush=True)
84
114
 
85
115
  def _keep(self, row: str, *, capped: bool) -> None:
@@ -122,8 +122,9 @@ matching section:
122
122
  diagnose a symlink/backup warning.
123
123
  - **D. PR body template** (release-handoff) — opt-in pre-registration; also
124
124
  offered on the first release-handoff run.
125
- - **E. final report language** (`reportLanguage`) — default is auto (follows
126
- the brief's language).
125
+ - **E. final report language** (`reportLanguage`) — a language tag (`en`,
126
+ `ko`, `fr`, ); default `en`. The Phase 7 translator translates the human
127
+ HTML into it.
127
128
  - **F. declared architecture style** (`architecture.style`) — `hexagonal` /
128
129
  `layered` / `none`; hand-added, default `none`. Declaring one makes that
129
130
  architecture's placement rules binding in planning + verification.
@@ -195,23 +195,22 @@ per-run override prompt during the next release-handoff run.
195
195
 
196
196
  ## E. Final report language (`reportLanguage`)
197
197
 
198
- The default is English. If you skip, no `reportLanguage` field is written and
199
- the runtime treats it as `auto` it follows the task brief's primary
200
- narration language (Korean brief Korean report, English brief English
201
- report).
198
+ The value is a language tag (`en`, `ko`, `fr`, `pt-BR`, …). The data.json
199
+ stays English; the Phase 7 translator translates the human HTML into the tag.
200
+ If you skip, no `reportLanguage` field is written and the runtime uses `en`.
202
201
 
203
202
  AskUserQuestion (fixed options):
204
203
  - Question: `"Which language should the final report be written in?"`
205
204
  - Options:
206
205
  1. `English (recommended)` → `en`
207
206
  2. `Korean` → `ko`
208
- 3. `Auto (infer from the task brief language; English if unclear)` `auto`
209
- 4. `Later` → skip (no field set → runtime treats it as auto)
207
+ 3. `Other` ask for the language tag (`fr`, `de`, `ja`, `pt-BR`, …)
208
+ 4. `Later` → skip (no field set → `en`)
210
209
 
211
210
  If the user picks 1/2/3:
212
211
 
213
212
  ```bash
214
- okstra config set report-language <en|ko|auto> --scope project
213
+ okstra config set report-language <language-tag> --scope project
215
214
  ```
216
215
 
217
216
  Set the global default manually with `--scope global` as described in the
@@ -74,10 +74,6 @@ from okstra_ctl.mutation_probe import ( # noqa: E402
74
74
  classify_reason,
75
75
  )
76
76
  from okstra_ctl.self_mock_signals import selfmock_path_key # noqa: E402
77
- from okstra_ctl.report_translation import ( # noqa: E402
78
- HANGUL_PROSE_LIMIT,
79
- hangul_share,
80
- )
81
77
  from okstra_ctl.validation_contract import ( # noqa: E402
82
78
  CURRENT_VALIDATION_CONTRACT_VERSION,
83
79
  )
@@ -6353,7 +6349,12 @@ def _plan_verify_dispatched_results(
6353
6349
  for row in payload.get("workerDispatches") or []:
6354
6350
  if not isinstance(row, dict):
6355
6351
  continue
6356
- if not str(row.get("kind") or "").startswith("reverify-r"):
6352
+ # critic 동수 라운드는 `kind: "critic"` 으로 나간다 — 결과 파일명은
6353
+ # 같은 `-plan-verify-r<N>-` 꼴이다. reverify 계열만 세면 동수가 있던
6354
+ # run 마다 critic 표가 "디스패치 기록 없음" 으로 오탐된다(2026-09-09,
6355
+ # fontsninja-v3-site dev-10627 planning 002).
6356
+ kind = str(row.get("kind") or "")
6357
+ if not (kind.startswith("reverify-r") or kind == "critic"):
6357
6358
  continue
6358
6359
  name = Path(str(row.get("workerResultPath") or "")).name
6359
6360
  if "-plan-verify-r" not in name:
@@ -8129,32 +8130,6 @@ def _validate_improvement_discovery(
8129
8130
  failures.append(f"improvement-discovery: {err}")
8130
8131
 
8131
8132
 
8132
- def _validate_ssot_is_english(data: dict, failures: list[str]) -> None:
8133
- """Enforce: the final-report data.json is authored in English.
8134
-
8135
- `meta.reportLanguage` names the language the human HTML renders in, and
8136
- Phase 7's translator serves it from a sidecar. The data.json itself is the
8137
- record every later phase, validator and agent reads, so a worker that
8138
- authors it in the reader's language instead splits the record — and does so
8139
- silently, because rendering, follow-up spawning and validation all succeed
8140
- on it.
8141
-
8142
- `report-finalize` runs the same check as its first step, before anything
8143
- derives from the report. This is the second gate, for a report that reached
8144
- validation by some other path.
8145
- """
8146
- if not data:
8147
- return
8148
- share, length = hangul_share(data)
8149
- if length and share >= HANGUL_PROSE_LIMIT:
8150
- failures.append(
8151
- f"final-report data.json was authored in Korean ({share:.0%} of its "
8152
- f"prose, limit {HANGUL_PROSE_LIMIT:.0%}). The data.json is the "
8153
- "English SSOT; meta.reportLanguage selects the human HTML's "
8154
- "language and is served by the Phase 7 translator sidecar."
8155
- )
8156
-
8157
-
8158
8133
  def _validate_translation_sidecar(
8159
8134
  data: Mapping[str, Any], report_path: Path, failures: list[str]
8160
8135
  ) -> None:
@@ -9429,7 +9404,6 @@ def main() -> int:
9429
9404
  ),
9430
9405
  )
9431
9406
  validation_data = report_data if isinstance(report_data, Mapping) else {}
9432
- _validate_ssot_is_english(validation_data, failures)
9433
9407
  _validate_translation_sidecar(
9434
9408
  validation_data, _data_path_for(report_path), failures
9435
9409
  )