@techierathore/techieflow 1.0.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.
Files changed (151) hide show
  1. package/.claude/commands/TechieFlow/agents/analyst.md +97 -0
  2. package/.claude/commands/TechieFlow/agents/architect.md +84 -0
  3. package/.claude/commands/TechieFlow/agents/flow-master.md +217 -0
  4. package/.claude/commands/TechieFlow/agents/verifier.md +72 -0
  5. package/.claude/commands/TechieFlow/tasks/_metrics-emit-gate.md +138 -0
  6. package/.claude/commands/TechieFlow/tasks/_smoke-test-policy.md +98 -0
  7. package/.claude/commands/TechieFlow/tasks/_status-update-gate.md +78 -0
  8. package/.claude/commands/TechieFlow/tasks/_yolo-mode.md +77 -0
  9. package/.claude/commands/TechieFlow/tasks/advanced-elicitation.md +117 -0
  10. package/.claude/commands/TechieFlow/tasks/amend-docs.md +149 -0
  11. package/.claude/commands/TechieFlow/tasks/author-brd.md +79 -0
  12. package/.claude/commands/TechieFlow/tasks/build-phase.md +231 -0
  13. package/.claude/commands/TechieFlow/tasks/create-deep-research-prompt.md +278 -0
  14. package/.claude/commands/TechieFlow/tasks/create-doc.md +101 -0
  15. package/.claude/commands/TechieFlow/tasks/day1-brownfield.md +507 -0
  16. package/.claude/commands/TechieFlow/tasks/day1-greenfield.md +224 -0
  17. package/.claude/commands/TechieFlow/tasks/devguide.md +224 -0
  18. package/.claude/commands/TechieFlow/tasks/document-project.md +343 -0
  19. package/.claude/commands/TechieFlow/tasks/execute-checklist.md +86 -0
  20. package/.claude/commands/TechieFlow/tasks/facilitate-brainstorming-session.md +136 -0
  21. package/.claude/commands/TechieFlow/tasks/fix-issues.md +134 -0
  22. package/.claude/commands/TechieFlow/tasks/generate-html.md +115 -0
  23. package/.claude/commands/TechieFlow/tasks/handoff-phase.md +126 -0
  24. package/.claude/commands/TechieFlow/tasks/index-docs.md +173 -0
  25. package/.claude/commands/TechieFlow/tasks/kb-mode-interaction.md +75 -0
  26. package/.claude/commands/TechieFlow/tasks/log-miss.md +160 -0
  27. package/.claude/commands/TechieFlow/tasks/metrics-report.md +143 -0
  28. package/.claude/commands/TechieFlow/tasks/mockups.md +122 -0
  29. package/.claude/commands/TechieFlow/tasks/productguide.md +105 -0
  30. package/.claude/commands/TechieFlow/tasks/refresh-status.md +135 -0
  31. package/.claude/commands/TechieFlow/tasks/render-workflow-docs.md +117 -0
  32. package/.claude/commands/TechieFlow/tasks/shard-doc.md +185 -0
  33. package/.claude/commands/TechieFlow/tasks/split-brd.md +131 -0
  34. package/.claude/commands/TechieFlow/tasks/triage-issues.md +157 -0
  35. package/.claude/commands/TechieFlow/tasks/verify-phase.md +579 -0
  36. package/.claude/commands/generate-html.md +124 -0
  37. package/.codex/config.toml +46 -0
  38. package/.codex/hooks.json +63 -0
  39. package/.codex/rules/techieflow.rules +8 -0
  40. package/.opencode/command/generate-html.md +129 -0
  41. package/.opencode/plugin/techieflow.js +350 -0
  42. package/.tfcore/TOKEN-GUIDE.md +59 -0
  43. package/.tfcore/agents/analyst.md +97 -0
  44. package/.tfcore/agents/architect.md +84 -0
  45. package/.tfcore/agents/flow-master.md +217 -0
  46. package/.tfcore/agents/verifier.md +72 -0
  47. package/.tfcore/checklists/architect-checklist.md +438 -0
  48. package/.tfcore/core-config.yaml +47 -0
  49. package/.tfcore/data/brainstorming-techniques.md +36 -0
  50. package/.tfcore/data/elicitation-methods.md +154 -0
  51. package/.tfcore/data/techieflow-kb.md +807 -0
  52. package/.tfcore/data/technical-preferences.md +3 -0
  53. package/.tfcore/enhanced-ide-development-workflow.md +248 -0
  54. package/.tfcore/hooks/block-git.sh +301 -0
  55. package/.tfcore/hooks/codex-adapter.py +248 -0
  56. package/.tfcore/hooks/guard-artifacts.sh +101 -0
  57. package/.tfcore/hooks/guard-status-html.sh +102 -0
  58. package/.tfcore/hooks/guard-status.sh +124 -0
  59. package/.tfcore/hooks/guard-verify.sh +108 -0
  60. package/.tfcore/hooks/metrics-session.sh +129 -0
  61. package/.tfcore/hooks/session-pointer.sh +52 -0
  62. package/.tfcore/hooks/sweep-artifacts.sh +198 -0
  63. package/.tfcore/install-manifest.yaml +231 -0
  64. package/.tfcore/routing.yaml +105 -0
  65. package/.tfcore/tasks/_metrics-emit-gate.md +138 -0
  66. package/.tfcore/tasks/_smoke-test-policy.md +98 -0
  67. package/.tfcore/tasks/_status-update-gate.md +78 -0
  68. package/.tfcore/tasks/_yolo-mode.md +77 -0
  69. package/.tfcore/tasks/advanced-elicitation.md +117 -0
  70. package/.tfcore/tasks/amend-docs.md +149 -0
  71. package/.tfcore/tasks/author-brd.md +79 -0
  72. package/.tfcore/tasks/build-phase.md +231 -0
  73. package/.tfcore/tasks/create-deep-research-prompt.md +278 -0
  74. package/.tfcore/tasks/create-doc.md +101 -0
  75. package/.tfcore/tasks/day1-brownfield.md +507 -0
  76. package/.tfcore/tasks/day1-greenfield.md +224 -0
  77. package/.tfcore/tasks/devguide.md +224 -0
  78. package/.tfcore/tasks/document-project.md +343 -0
  79. package/.tfcore/tasks/execute-checklist.md +86 -0
  80. package/.tfcore/tasks/facilitate-brainstorming-session.md +136 -0
  81. package/.tfcore/tasks/fix-issues.md +134 -0
  82. package/.tfcore/tasks/generate-html.md +115 -0
  83. package/.tfcore/tasks/handoff-phase.md +126 -0
  84. package/.tfcore/tasks/index-docs.md +173 -0
  85. package/.tfcore/tasks/kb-mode-interaction.md +75 -0
  86. package/.tfcore/tasks/log-miss.md +160 -0
  87. package/.tfcore/tasks/metrics-report.md +143 -0
  88. package/.tfcore/tasks/mockups.md +122 -0
  89. package/.tfcore/tasks/productguide.md +105 -0
  90. package/.tfcore/tasks/refresh-status.md +135 -0
  91. package/.tfcore/tasks/render-workflow-docs.md +117 -0
  92. package/.tfcore/tasks/shard-doc.md +185 -0
  93. package/.tfcore/tasks/split-brd.md +131 -0
  94. package/.tfcore/tasks/triage-issues.md +157 -0
  95. package/.tfcore/tasks/verify-phase.md +579 -0
  96. package/.tfcore/telemetry/SCHEMA.md +678 -0
  97. package/.tfcore/telemetry/install-metrics.sh +390 -0
  98. package/.tfcore/telemetry/pre-commit +94 -0
  99. package/.tfcore/telemetry/tf-metrics.sh +1415 -0
  100. package/.tfcore/templates/architecture-tmpl.yaml +650 -0
  101. package/.tfcore/templates/brainstorming-output-tmpl.yaml +156 -0
  102. package/.tfcore/templates/brownfield-architecture-tmpl.yaml +476 -0
  103. package/.tfcore/templates/competitor-analysis-tmpl.yaml +306 -0
  104. package/.tfcore/templates/front-end-architecture-tmpl.yaml +218 -0
  105. package/.tfcore/templates/fullstack-architecture-tmpl.yaml +823 -0
  106. package/.tfcore/templates/market-research-tmpl.yaml +252 -0
  107. package/.tfcore/templates/project-brief-tmpl.yaml +221 -0
  108. package/.tfcore/templates/v4custom/app-agents-md-tmpl.md +61 -0
  109. package/.tfcore/templates/v4custom/app-architecture-tmpl.md +127 -0
  110. package/.tfcore/templates/v4custom/app-brd-tmpl.md +204 -0
  111. package/.tfcore/templates/v4custom/app-checklist-tmpl.md +90 -0
  112. package/.tfcore/templates/v4custom/app-claude-md-tmpl.md +33 -0
  113. package/.tfcore/templates/v4custom/app-devguide-tmpl.md +167 -0
  114. package/.tfcore/templates/v4custom/app-editorconfig-tmpl.editorconfig +44 -0
  115. package/.tfcore/templates/v4custom/app-library-feedback-tmpl.md +31 -0
  116. package/.tfcore/templates/v4custom/app-productguide-tmpl.md +54 -0
  117. package/.tfcore/templates/v4custom/app-project-status-tmpl.md +98 -0
  118. package/.tfcore/templates/v4custom/app-uidesign-tmpl.md +48 -0
  119. package/.tfcore/templates/v4custom/app-usageguide-tmpl.md +67 -0
  120. package/.tfcore/templates/v4custom/build-invocation-ladder.md +190 -0
  121. package/.tfcore/templates/v4custom/html-render-shell.md +502 -0
  122. package/.tfcore/templates/v4custom/metrics-report-template.md +273 -0
  123. package/.tfcore/templates/v4custom/opencode-operating-contract.md +56 -0
  124. package/.tfcore/user-guide.md +633 -0
  125. package/.tfcore/utils/techieflow-doc-template.md +325 -0
  126. package/.tfcore/utils/tf-assets.sh +310 -0
  127. package/.tfcore/utils/tf-codex-bind.py +196 -0
  128. package/.tfcore/utils/tf-codex-telemetry.py +60 -0
  129. package/.tfcore/utils/tf-emit.sh +1075 -0
  130. package/.tfcore/utils/tf-gitignore-audit.sh +281 -0
  131. package/.tfcore/utils/tf-goal.sh +421 -0
  132. package/.tfcore/utils/tf-harness.sh +160 -0
  133. package/.tfcore/utils/tf-mockup-parity.mjs +555 -0
  134. package/.tfcore/utils/tf-mockup-parity.sh +65 -0
  135. package/.tfcore/utils/tf-opencode-audit.py +126 -0
  136. package/.tfcore/utils/tf-perf.sh +241 -0
  137. package/.tfcore/utils/tf-render-html.py +715 -0
  138. package/.tfcore/utils/tf-render-html.sh +28 -0
  139. package/.tfcore/utils/tf-routing-bind.sh +216 -0
  140. package/.tfcore/utils/tf-routing.sh +247 -0
  141. package/.tfcore/utils/tf-yolo.sh +119 -0
  142. package/.tfcore/working-in-the-brownfield.md +606 -0
  143. package/LICENSE +201 -0
  144. package/README.md +1212 -0
  145. package/WORKFLOW.html +1986 -0
  146. package/docs/TechieFlow-Installation.md +299 -0
  147. package/opencode.jsonc +929 -0
  148. package/package.json +60 -0
  149. package/scripts/install.mjs +992 -0
  150. package/scripts/test-install.mjs +505 -0
  151. package/scripts/validate.mjs +155 -0
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env python3
2
+ """Generate TechieFlow's Codex agents and thin workflow skills."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import pathlib
8
+ import re
9
+
10
+
11
+ PHASES = {
12
+ "day1-brownfield": ("analyst", "Reverse-document and initialize an existing application."),
13
+ "day1-greenfield": ("analyst", "Initialize planning artifacts and mockups for a new application."),
14
+ "amend-docs": ("analyst", "Amend existing requirements and design artifacts in place."),
15
+ "author-brd": ("analyst", "Interactively author or extend numbered business requirements."),
16
+ "mockups": ("analyst", "Create or update the greenfield UI design and HTML mockups."),
17
+ "split-brd": ("analyst", "Convert a BRD into the single implementation checklist."),
18
+ "build-phase": ("flow_master", "Implement the open checklist and chain smoke and verification."),
19
+ "verify-phase": ("verifier", "Independently verify requirements against runtime evidence."),
20
+ "fix-issues": ("flow_master", "Fix reported or verifier-discovered application defects."),
21
+ "triage-issues": ("flow_master", "Analyze and document human-found bugs without fixing source."),
22
+ "log-miss": ("flow_master", "Record one missed requirement as telemetry and a checklist line."),
23
+ "devguide": ("flow_master", "Create the developer-facing screen and component guide."),
24
+ "productguide": ("flow_master", "Create the user-facing guide from the running application."),
25
+ "handoff-phase": ("flow_master", "Complete handoff documents, status, and rendered artifacts."),
26
+ "refresh-status": ("flow_master", "Recover truthful project status after an interrupted run."),
27
+ "generate-html": ("flow_master", "Render an arbitrary human-readable Markdown document to HTML."),
28
+ "render-workflow-docs": ("flow_master", "Render the canonical BRD, Architecture, and status HTML files."),
29
+ "metrics-report": ("flow_master", "Report TechieFlow development telemetry without changing code."),
30
+ }
31
+
32
+ PERSONAS = {
33
+ "flow-master": "flow-master.md",
34
+ "analyst": "analyst.md",
35
+ "architect": "architect.md",
36
+ "verifier": "verifier.md",
37
+ }
38
+
39
+ WORKERS = {
40
+ "tf-builder": """Implement one assigned FN/NFR requirement cluster. Read `.tfcore/tasks/_smoke-test-policy.md`. Never run git or gh. Follow project coding standards, smoke the changed behavior, update only the assigned checklist rows, and return structured evidence to the parent.""",
41
+ "tf-test-writer": """Write black-box verification tests for one assigned requirement cluster. Never edit application source and never run git or gh. Follow `.tfcore/tasks/verify-phase.md` and return tests added, tests refreshed, and anything unobservable.""",
42
+ "tf-explorer": """Perform a focused read-only codebase scan. Never edit files and never run git or gh. Return concise evidence with file paths and symbols.""",
43
+ "trblazeui": """Read the NuGet-deployed TrBlazeUI persona and adopt its implementation rules. Resolve it in this order and use the FIRST that exists: `.claude/commands/trblazeui.md` (current deploy target), `.claude/trblazeui.md` (legacy deploy target), `.opencode/command/trblazeui.md`, `.trblazeui/TrBlazeUI-AI-Reference.md` (packaged component reference). Only if NONE exists, report that the TrBlazeUI persona is not deployed (`dotnet build` the app to unpack the package) and stop. Never run git or gh.""",
44
+ "techierag": """Read the NuGet-deployed TechieRag persona and adopt its implementation rules. Resolve it in this order and use the FIRST that exists: `.claude/commands/techierag.md` (current deploy target), `.claude/techierag.md` (legacy deploy target), `.opencode/command/techierag.md`, `.techierag/TechieRag-AI-Reference.md` (packaged service reference). Only if NONE exists, report that the TechieRag persona is not deployed (`dotnet build` the app to unpack the package) and stop. Never run git or gh.""",
45
+ }
46
+
47
+ # Library-persona workers are COMPATIBILITY wrappers: the library NuGet packages own
48
+ # the native `.codex/agents/<lib>.toml` (TrBlazeUI.Components >= 2.0.3 deploys it on
49
+ # `dotnet build`; TechieRag is expected to follow the same contract). The package's
50
+ # MSBuild target deploys only when the file is absent OR the ownership marker
51
+ # `.<lib>/.codex-agent-package-owned` exists; otherwise it preserves what it finds as
52
+ # consumer-owned. So the framework must (a) never overwrite a file that is not its own
53
+ # wrapper, and (b) write the marker alongside its wrapper so the package is allowed to
54
+ # replace it. The wrapper then self-retires per repo on the first post-2.0.3 build.
55
+ COMPAT = {
56
+ "trblazeui": ("TrBlazeUI.Components", ".trblazeui"),
57
+ "techierag": ("TechieRag", ".techierag"),
58
+ }
59
+ MARKER = ".codex-agent-package-owned"
60
+
61
+
62
+ def own_wrapper(path: pathlib.Path, name: str) -> bool:
63
+ """True when `path` is absent or holds the framework's own compat wrapper."""
64
+ if not path.exists():
65
+ return True
66
+ return f'description = "TechieFlow {name} role."' in path.read_text(encoding="utf-8")
67
+
68
+
69
+ def routing(path: pathlib.Path) -> dict:
70
+ cfg = {"enabled": False, "tiers": {}, "phases": {}, "subagents": {}, "effort": {}}
71
+ section = tier = None
72
+ if not path.exists():
73
+ return cfg
74
+ for line in path.read_text(encoding="utf-8").splitlines():
75
+ if not line.strip() or line.lstrip().startswith("#"):
76
+ continue
77
+ if not line.startswith(" "):
78
+ key, _, value = line.partition(":")
79
+ section, tier = key.strip(), None
80
+ if section == "enabled":
81
+ cfg["enabled"] = value.strip() == "true"
82
+ elif section == "tiers":
83
+ if re.match(r"^ [a-z-]+:\s*$", line):
84
+ tier = line.strip()[:-1]
85
+ cfg["tiers"][tier] = {}
86
+ elif tier and line.startswith(" "):
87
+ key, _, value = line.strip().partition(":")
88
+ cfg["tiers"][tier][key] = value.strip()
89
+ elif section in ("phases", "subagents", "effort"):
90
+ key, _, value = line.strip().partition(":")
91
+ cfg[section][key] = value.strip()
92
+ return cfg
93
+
94
+
95
+ def quoted(value: str) -> str:
96
+ return '"' + value.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + '"'
97
+
98
+
99
+ def write(path: pathlib.Path, content: str) -> None:
100
+ path.parent.mkdir(parents=True, exist_ok=True)
101
+ path.write_text(content.rstrip() + "\n", encoding="utf-8", newline="\n")
102
+
103
+
104
+ def persona_instructions(root: pathlib.Path, source_name: str) -> str:
105
+ source = root / ".tfcore" / "agents" / source_name
106
+ return f"""Read `{source.relative_to(root)}` completely before acting and follow its persona, core principles, command routing, and dependency rules. Skip only its activation greeting/help/halt ritual because this is a spawned Codex role. Treat `.tfcore/tasks/*.md` as executable workflows. Never run git or gh. Preserve TechieFlow's smoke, verifier, artifact-location, local-only runtime, checklist, and PROJECT-STATUS invariants. Load only dependencies needed for the assigned task."""
107
+
108
+
109
+ def agent_file(name: str, description: str, instructions: str, model: str | None,
110
+ effort: str | None, read_only: bool = False) -> str:
111
+ lines = [f"name = {quoted(name)}", f"description = {quoted(description)}",
112
+ f"developer_instructions = {quoted(instructions)}"]
113
+ if model:
114
+ lines.append(f"model = {quoted(model)}")
115
+ if effort:
116
+ lines.append(f"model_reasoning_effort = {quoted(effort)}")
117
+ if read_only:
118
+ lines.append('sandbox_mode = "read-only"')
119
+ return "\n".join(lines)
120
+
121
+
122
+ def main() -> int:
123
+ parser = argparse.ArgumentParser()
124
+ parser.add_argument("root", nargs="?", default=".")
125
+ args = parser.parse_args()
126
+ root = pathlib.Path(args.root).resolve()
127
+ if not (root / ".tfcore").is_dir():
128
+ parser.error(f"not a TechieFlow repository: {root}")
129
+ cfg = routing(root / ".tfcore" / "routing.yaml")
130
+
131
+ persona_tier = {"flow-master": "build-phase", "analyst": "day1-greenfield",
132
+ "architect": "day1-greenfield", "verifier": "verify-phase"}
133
+ for name, source in PERSONAS.items():
134
+ tier = cfg["phases"].get(persona_tier[name], "inherit")
135
+ model = cfg["tiers"].get(tier, {}).get("codex") if cfg["enabled"] and tier != "inherit" else None
136
+ effort = cfg["effort"].get(tier) if model else None
137
+ write(root / ".codex" / "agents" / f"{name}.toml", agent_file(
138
+ name.replace("-", "_"), f"TechieFlow {name} specialist.",
139
+ persona_instructions(root, source), model, effort))
140
+
141
+ kept = []
142
+ for name, instructions in WORKERS.items():
143
+ tier = cfg["subagents"].get(name, "inherit")
144
+ model = cfg["tiers"].get(tier, {}).get("codex") if cfg["enabled"] and tier != "inherit" else None
145
+ effort = cfg["effort"].get(tier) if model else None
146
+ target = root / ".codex" / "agents" / f"{name}.toml"
147
+ if name in COMPAT:
148
+ package, lib_dir = COMPAT[name]
149
+ if not own_wrapper(target, name):
150
+ kept.append(name) # package- or consumer-owned agent: never overwrite
151
+ continue
152
+ marker = root / lib_dir / MARKER
153
+ if not marker.exists():
154
+ write(marker, package)
155
+ ignore = root / lib_dir / ".gitignore" # same `*` the package writes on build
156
+ if not ignore.exists():
157
+ write(ignore, "*")
158
+ write(target, agent_file(
159
+ name.replace("-", "_"), f"TechieFlow {name} role.", instructions,
160
+ model, effort, read_only=name == "tf-explorer"))
161
+
162
+ for phase, (owner, description) in PHASES.items():
163
+ skill_name = f"techieflow-{phase.removesuffix('-phase')}"
164
+ task = f".tfcore/tasks/{phase}.md"
165
+ body = f"""---
166
+ name: {skill_name}
167
+ description: {description} Use when a TechieFlow project needs its `{phase}` workflow.
168
+ ---
169
+
170
+ # {skill_name}
171
+
172
+ 1. Read `{task}` completely and follow it as the canonical executable workflow.
173
+ 2. Read `.tfcore/core-config.yaml` and only the task dependencies required for this run.
174
+ 3. Operate as the `{owner}` role. Delegate only where the task explicitly calls for independent subagents, using the registered Codex roles and waiting for their results.
175
+ 4. Preserve interactive elicitation unless `.tfcore/.session/yolo.json` exists or the user explicitly requested YOLO/goal mode.
176
+ 5. Never run `git` or `gh`. Run required builds, smoke tests, runtime observations, verifier gates, document updates, and telemetry yourself.
177
+ 6. Treat old `*...` and harness slash-command text in the canonical task as vocabulary aliases; execute the named task or delegate to the named Codex role directly.
178
+ """
179
+ write(root / ".agents" / "skills" / skill_name / "SKILL.md", body)
180
+
181
+ yolo = """---
182
+ name: techieflow-yolo
183
+ description: Enable, disable, or inspect TechieFlow YOLO mode for an explicitly requested unattended workflow.
184
+ ---
185
+
186
+ Read `.tfcore/tasks/_yolo-mode.md` completely. Run `bash .tfcore/utils/tf-yolo.sh on|off` as requested. YOLO removes elicitation pauses but does not broaden the user's task, allow git/gh, bypass the Codex workspace sandbox, or waive genuine external blockers. For a supervised long-running goal, use `.tfcore/utils/tf-goal.sh --harness codex`.
187
+ """
188
+ write(root / ".agents" / "skills" / "techieflow-yolo" / "SKILL.md", yolo)
189
+ print(f"tf-codex-bind: generated {len(PERSONAS) + len(WORKERS) - len(kept)} agents and {len(PHASES) + 1} skills")
190
+ for name in kept:
191
+ print(f"tf-codex-bind: kept library-owned .codex/agents/{name}.toml (compat wrapper retired)")
192
+ return 0
193
+
194
+
195
+ if __name__ == "__main__":
196
+ raise SystemExit(main())
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env python3
2
+ """Emit authoritative Codex exec JSONL usage into TechieFlow sessions telemetry."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ import pathlib
9
+ import subprocess
10
+ import sys
11
+
12
+
13
+ def main() -> int:
14
+ if len(sys.argv) != 3:
15
+ print("usage: tf-codex-telemetry.py <project-root> <codex-jsonl>", file=sys.stderr)
16
+ return 2
17
+ root = pathlib.Path(sys.argv[1]).resolve()
18
+ source = pathlib.Path(sys.argv[2])
19
+ thread_id = None
20
+ model = None
21
+ totals = {"input_tokens": 0, "output_tokens": 0, "cache_read_tokens": 0,
22
+ "reasoning_output_tokens": 0}
23
+ try:
24
+ for line in source.read_text(encoding="utf-8", errors="replace").splitlines():
25
+ try:
26
+ event = json.loads(line)
27
+ except Exception:
28
+ continue
29
+ if event.get("type") == "thread.started":
30
+ thread_id = event.get("thread_id") or thread_id
31
+ if event.get("model"):
32
+ model = event.get("model")
33
+ if event.get("type") == "turn.completed" and isinstance(event.get("usage"), dict):
34
+ usage = event["usage"]
35
+ totals["input_tokens"] += int(usage.get("input_tokens") or 0)
36
+ totals["output_tokens"] += int(usage.get("output_tokens") or 0)
37
+ totals["cache_read_tokens"] += int(usage.get("cached_input_tokens") or 0)
38
+ totals["reasoning_output_tokens"] += int(usage.get("reasoning_output_tokens") or 0)
39
+ except Exception:
40
+ return 0
41
+ if not thread_id or not any(totals.values()):
42
+ return 0
43
+ record = {
44
+ "kind": "session", "session_id": thread_id, "model": model,
45
+ "duration_s": None, **totals, "cache_creation_tokens": None,
46
+ "cost_usd": None, "children_sessions": None,
47
+ }
48
+ emit = root / ".tfcore" / "utils" / "tf-emit.sh"
49
+ env = os.environ.copy()
50
+ env.update(TF_HARNESS="codex", TF_PROJECT_DIR=str(root), CLAUDE_PROJECT_DIR=str(root))
51
+ try:
52
+ subprocess.run(["bash", str(emit), "sessions"], input=json.dumps(record), text=True,
53
+ timeout=10, env=env, check=False)
54
+ except Exception:
55
+ pass
56
+ return 0
57
+
58
+
59
+ if __name__ == "__main__":
60
+ raise SystemExit(main())