* enhance(#2573): stamp STATE.md with its commit and surface a commit-age freshness hint Adds a `state_head` stamp to STATE.md and derives a tri-state commit-age freshness proxy (state_commits_behind / state_commit_stale) through state.cjs's readStateHeadFreshness, surfaced on smart-entry signals and as health W024. The proxy is advisory: classify() deliberately does NOT consume it (ADR-1787 locks the classification/routing boundary — a signal, not a route). Composes with #3099 and #1882 (both merged to next after this branch): the commit-age proxy reads `state_head` while the LAST_ACTIVITY_UNPARSEABLE diagnostic reads `last_activity` — two different fields, not "two staleness signals on one field." A new regression test asserts a STATE.md carrying both an unparseable last_activity AND a valid state_head resolves each independently (diagnostic fires once; freshness reads state_head, commits_behind 0). Rebased onto next (flattened): resolved the add/add conflicts in src/smart-entry.cts (kept both the #2573 freshness import/derivation and the #3099 diagnostic import/call) and tests/smart-entry.unit.test.cjs (kept both describe blocks). Drift-ack for health.md's W024 row is unchanged (12348 B). Tests: smart-entry 62, state/state-transition/health/verify 639, all pass. * chore(#2573): allowlist health-validation test in the prompt-injection scan The scanner's `exec('` code-execution pattern matches the benign `re.exec('<phase-id>')` RegExp method calls in the phase-ID grammar tests (pre-existing: 16 such calls on next, this PR adds none). The file entered the diff-mode scan's changed-file set only because #2573's W024 state_head assertions touch it. Allowlist it alongside the other test files that carry pattern-matching content as data (same DEFECT.PROMPT-INJECTION-SCAN-COLLISION class). Scanner self-test 38/0; diff scan 14 files, 0 findings.
This commit is contained in:
@@ -1000,6 +1000,18 @@ v1.40.0, [#2792](https://github.com/open-gsd/gsd-core/issues/2792)).
|
||||
/gsd-health --context # Context-utilization triage
|
||||
```
|
||||
|
||||
**STATE.md freshness (`W024`).** STATE.md records the commit it was last written
|
||||
against (`state_head` in its frontmatter). When the codebase has moved a long way
|
||||
since — 20 commits or more — health adds an advisory noting that STATE.md's
|
||||
contents should be treated as approximate.
|
||||
|
||||
This is a *freshness proxy, not a drift measurement*: the count includes commits
|
||||
that never touched anything STATE.md describes, and the stamp is refreshed by any
|
||||
command that writes STATE.md, so a low count means STATE.md was written recently
|
||||
rather than that its contents are correct. The advisory never changes health's
|
||||
pass/fail status, and stays silent when the stamp is absent or the project isn't
|
||||
a git repo — "unknown" is reported as unknown, not as fresh.
|
||||
|
||||
### `/gsd-cleanup`
|
||||
|
||||
Archive accumulated phase directories from completed milestones and prune local branches whose upstream has been deleted.
|
||||
|
||||
@@ -45,6 +45,7 @@ current_phase: "4"
|
||||
current_phase_name: Observability
|
||||
current_plan: "3"
|
||||
last_updated: "2026-06-01T12:34:56.789Z"
|
||||
state_head: 4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
|
||||
last_activity: "2026-06-01"
|
||||
stopped_at: "Phase 4 P3 execution complete"
|
||||
paused_at: null
|
||||
@@ -71,6 +72,7 @@ paused_at: null
|
||||
| `current_phase_name` | string | フェーズに名前がある場合 | 本文の `Current Phase Name:` フィールドから抽出したフェーズ名。 |
|
||||
| `current_plan` | string | プランが進行中の場合 | 本文の `Current Plan:` フィールドから抽出したプラン番号。 |
|
||||
| `last_updated` | ISO-8601 タイムスタンプ | 書き込み時に常時 | 最後の `syncStateFrontmatter` 呼び出しのタイムスタンプ。`realClock.nowIso()` によって書き込まれる。 |
|
||||
| `state_head` | string (40-char sha) | On write, when the project's own git repo resolves | Full commit sha STATE.md was written against (#2573). Omitted entirely outside a git repo, or when the resolved repo is not the project's own — an unverifiable stamp degrades to absent rather than asserting provenance the file does not have. Recomputed on every write and never carried forward. |
|
||||
| `last_activity` | string | 本文に設定されている場合 | 本文の `Last Activity:` フィールドから抽出した最終活動日。 |
|
||||
| `stopped_at` | string | 停止ポイントが記録された場合 | 最後に完了したアクションの説明。アーカイブの文章とのマッチを避けるため `## Session` 本文セクションにスコープを限定。 |
|
||||
| `paused_at` | string | プロジェクトが一時停止中の場合 | 一時停止ポイントの自由形式の説明。一時停止していない場合は省略または `null`。 |
|
||||
|
||||
@@ -45,6 +45,7 @@ current_phase: "4"
|
||||
current_phase_name: Observability
|
||||
current_plan: "3"
|
||||
last_updated: "2026-06-01T12:34:56.789Z"
|
||||
state_head: 4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
|
||||
last_activity: "2026-06-01"
|
||||
stopped_at: "Phase 4 P3 execution complete"
|
||||
paused_at: null
|
||||
@@ -71,6 +72,7 @@ paused_at: null
|
||||
| `current_phase_name` | string | 페이즈에 이름이 있는 경우 | 본문 `Current Phase Name:` 필드에서 추출된 페이즈 이름. |
|
||||
| `current_plan` | string | 플랜이 진행 중인 경우 | 본문 `Current Plan:` 필드에서 추출된 플랜 번호. |
|
||||
| `last_updated` | ISO-8601 타임스탬프 | 항상 (쓰기 시) | 마지막 `syncStateFrontmatter` 호출의 타임스탬프. `realClock.nowIso()`에 의해 기록됩니다. |
|
||||
| `state_head` | string (40-char sha) | On write, when the project's own git repo resolves | Full commit sha STATE.md was written against (#2573). Omitted entirely outside a git repo, or when the resolved repo is not the project's own — an unverifiable stamp degrades to absent rather than asserting provenance the file does not have. Recomputed on every write and never carried forward. |
|
||||
| `last_activity` | string | 본문에 설정된 경우 | 본문 `Last Activity:` 필드에서 추출된 마지막 활동 날짜. |
|
||||
| `stopped_at` | string | 중단점이 기록된 경우 | 마지막으로 완료된 작업의 설명. 아카이브 산문과의 매칭을 피하기 위해 `## Session` 본문 섹션으로 범위가 제한됩니다. |
|
||||
| `paused_at` | string | 프로젝트가 일시 정지된 경우 | 일시 정지 지점에 대한 자유형 설명. 일시 정지 상태가 아닐 때는 없거나 `null`. |
|
||||
|
||||
@@ -45,6 +45,7 @@ current_phase: "4"
|
||||
current_phase_name: Observability
|
||||
current_plan: "3"
|
||||
last_updated: "2026-06-01T12:34:56.789Z"
|
||||
state_head: 4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
|
||||
last_activity: "2026-06-01"
|
||||
stopped_at: "Phase 4 P3 execution complete"
|
||||
paused_at: null
|
||||
@@ -71,6 +72,7 @@ paused_at: null
|
||||
| `current_phase_name` | string | Quando uma fase tem nome | Nome da fase extraído do campo `Current Phase Name:` do corpo. |
|
||||
| `current_plan` | string | Quando um plano está em andamento | Número do plano extraído do campo `Current Plan:` do corpo. |
|
||||
| `last_updated` | timestamp ISO-8601 | Sempre (na escrita) | Timestamp da última chamada a `syncStateFrontmatter`; escrito por `realClock.nowIso()`. |
|
||||
| `state_head` | string (40-char sha) | On write, when the project's own git repo resolves | Full commit sha STATE.md was written against (#2573). Omitted entirely outside a git repo, or when the resolved repo is not the project's own — an unverifiable stamp degrades to absent rather than asserting provenance the file does not have. Recomputed on every write and never carried forward. |
|
||||
| `last_activity` | string | Quando definido no corpo | Data da última atividade, extraída do campo `Last Activity:` do corpo. |
|
||||
| `stopped_at` | string | Quando um ponto de parada foi registrado | Descrição da última ação concluída; limitada à seção `## Session` do corpo para evitar correspondência com prosa de arquivo. |
|
||||
| `paused_at` | string | Quando o projeto está pausado | Descrição de forma livre do ponto de pausa; ausente ou `null` quando não pausado. |
|
||||
|
||||
@@ -45,6 +45,7 @@ current_phase: "4"
|
||||
current_phase_name: Observability
|
||||
current_plan: "3"
|
||||
last_updated: "2026-06-01T12:34:56.789Z"
|
||||
state_head: 4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
|
||||
last_activity: "2026-06-01"
|
||||
stopped_at: "Phase 4 P3 execution complete"
|
||||
paused_at: null
|
||||
@@ -71,10 +72,20 @@ paused_at: null
|
||||
| `current_phase_name` | string | When a phase has a name | Phase name extracted from the body `Current Phase Name:` field. |
|
||||
| `current_plan` | string | When a plan is in progress | Plan number extracted from the body `Current Plan:` field. |
|
||||
| `last_updated` | ISO-8601 timestamp | Always (on write) | Timestamp of the last `syncStateFrontmatter` call; written by `realClock.nowIso()`. |
|
||||
| `state_head` | string (40-char sha) | On write, when the project's own git repo resolves | Full commit sha STATE.md was written against (#2573). Omitted entirely outside a git repo, when the resolved repo is not the project's own, or in a `planning.sub_repos` workspace — an unverifiable stamp degrades to absent rather than asserting provenance the file does not have. Recomputed on every write and never carried forward. |
|
||||
| `last_activity` | string | When set in body | Date of the last activity, extracted from the body `Last Activity:` field. |
|
||||
| `stopped_at` | string | When a stop point was recorded | Description of the last completed action; scoped to the `## Session` body section to avoid matching archive prose. |
|
||||
| `paused_at` | string | When the project is paused | Freeform description of the pause point; absent or `null` when not paused. |
|
||||
|
||||
> **Known limitation — multi-repo workspaces.** In a workspace configured with
|
||||
> [`planning.sub_repos`](../CONFIGURATION.md#planning), the freshness hint reports *unknown*
|
||||
> rather than a commit age, and `state_head` is omitted. The outer workspace can own both
|
||||
> `.planning/` and its own git repo while every code commit lands in a nested child repo, so the
|
||||
> outer `HEAD` would not advance when the code does — measuring against it would report
|
||||
> "known fresh" for a STATE.md that is arbitrarily far behind. Reporting unknown is deliberate:
|
||||
> a wrong answer here is worse than no answer. Aggregating freshness across several child
|
||||
> histories needs a defined semantics and is not part of this feature.
|
||||
|
||||
### Status values
|
||||
|
||||
`normalizeStateStatus()` in `gsd-core/bin/lib/state-document.cjs` maps raw body text to these canonical values:
|
||||
|
||||
@@ -45,6 +45,7 @@ current_phase: "4"
|
||||
current_phase_name: Observability
|
||||
current_plan: "3"
|
||||
last_updated: "2026-06-01T12:34:56.789Z"
|
||||
state_head: 4f3c2b1a9e8d7c6b5a4f3e2d1c0b9a8f7e6d5c4b
|
||||
last_activity: "2026-06-01"
|
||||
stopped_at: "Phase 4 P3 execution complete"
|
||||
paused_at: null
|
||||
@@ -71,6 +72,7 @@ paused_at: null
|
||||
| `current_phase_name` | 字符串 | 阶段有名称时 | 从正文 `Current Phase Name:` 字段提取的阶段名称。 |
|
||||
| `current_plan` | 字符串 | 计划进行中时 | 从正文 `Current Plan:` 字段提取的计划编号。 |
|
||||
| `last_updated` | ISO-8601 时间戳 | 始终(写入时) | 最后一次 `syncStateFrontmatter` 调用的时间戳;由 `realClock.nowIso()` 写入。 |
|
||||
| `state_head` | string (40-char sha) | On write, when the project's own git repo resolves | Full commit sha STATE.md was written against (#2573). Omitted entirely outside a git repo, or when the resolved repo is not the project's own — an unverifiable stamp degrades to absent rather than asserting provenance the file does not have. Recomputed on every write and never carried forward. |
|
||||
| `last_activity` | 字符串 | 正文中设置时 | 最后活动日期,从正文 `Last Activity:` 字段提取。 |
|
||||
| `stopped_at` | 字符串 | 记录了停止点时 | 最后完成操作的描述;限定在 `## Session` 正文章节内,以避免匹配存档文本。 |
|
||||
| `paused_at` | 字符串 | 项目已暂停时 | 暂停点的自由描述;未暂停时缺失或为 `null`。 |
|
||||
|
||||
Reference in New Issue
Block a user