Skip to content
Closed
240 changes: 240 additions & 0 deletions .github/workflows/hourly-product-development.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
name: Hourly ScopeWeave Product Development

on:
schedule:
- cron: "41 * * * *"
workflow_dispatch:

permissions:
contents: read
pull-requests: read

concurrency:
group: scopeweave-hourly-product-development
cancel-in-progress: false

jobs:
create-one-bounded-product-task:
runs-on: ubuntu-latest
timeout-minutes: 10
env:
TARGET_REPOSITORY: ContextualWisdomLab/scopeweave
BASE_BRANCH: develop
AGENT_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
steps:
- name: Determine whether product development may start
id: gate
env:
GH_TOKEN: ${{ github.token }}
shell: bash
run: |
set -euo pipefail

if [ -z "${AGENT_TOKEN:-}" ]; then
echo "::warning::COPILOT_GITHUB_TOKEN is not configured; product development remains fail-closed."
echo "eligible=false" >>"$GITHUB_OUTPUT"
exit 0
fi

open_pr_count="$(
gh api "/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1" \
--jq 'length'
)"
if [ "$open_pr_count" -ne 0 ]; then
echo "An open pull request already owns the development queue."
echo "eligible=false" >>"$GITHUB_OUTPUT"
exit 0
fi

if ! tasks_json="$(
GH_TOKEN="$AGENT_TOKEN" gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--paginate \
--slurp \
"/agents/repos/${TARGET_REPOSITORY}/tasks?per_page=100"
)"; then
echo "::warning::Unable to list Copilot agent tasks; refusing to create another."
echo "eligible=false" >>"$GITHUB_OUTPUT"
exit 0
fi

if ! active_task_count="$(
TASKS_JSON="$tasks_json" python3 - <<'PY'
import json
import os
import sys

payload = json.loads(os.environ["TASKS_JSON"])

def extract_tasks(value):
"""Return a flat task list or None for an unknown response shape."""
if isinstance(value, list):
if all(isinstance(item, dict) and "state" in item for item in value):
return value
extracted = []
for page in value:
page_tasks = extract_tasks(page)
if page_tasks is None:
return None
extracted.extend(page_tasks)
return extracted
if isinstance(value, dict):
tasks_value = value.get("tasks", value.get("items"))
if isinstance(tasks_value, list):
return extract_tasks(tasks_value)
return None

tasks = extract_tasks(payload)
if tasks is None:
print("Unsupported agent-task response shape", file=sys.stderr)
raise SystemExit(1)

active_states = {"queued", "in_progress", "idle", "waiting_for_user"}
terminal_states = {"completed", "failed", "timed_out", "cancelled"}
active_count = 0
for task in tasks:
if not isinstance(task, dict):
active_count += 1
continue
state = task.get("state")
if state in active_states or state not in terminal_states:
active_count += 1
print(active_count)
PY
)"; then
echo "::warning::Unable to interpret Copilot agent tasks; refusing to create another."
echo "eligible=false" >>"$GITHUB_OUTPUT"
exit 0
fi

if [ "$active_task_count" -ne 0 ]; then
echo "A Copilot agent task already owns the development queue."
echo "eligible=false" >>"$GITHUB_OUTPUT"
exit 0
fi

echo "eligible=true" >>"$GITHUB_OUTPUT"

- name: Revalidate the single-flight gate and create one product task
if: steps.gate.outputs.eligible == 'true'
env:
GH_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
shell: bash
run: |
set -euo pipefail

open_pr_count="$(
GH_TOKEN="${{ github.token }}" gh api \
"/repos/${TARGET_REPOSITORY}/pulls?state=open&per_page=1" \
--jq 'length'
)"
if [ "$open_pr_count" -ne 0 ]; then
echo "A pull request appeared after the first gate; refusing duplicate development."
exit 0
fi

tasks_json="$(
gh api \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
--paginate \
--slurp \
"/agents/repos/${TARGET_REPOSITORY}/tasks?per_page=100"
)"
TASKS_JSON="$tasks_json" python3 - <<'PY'
import json
import os

payload = json.loads(os.environ["TASKS_JSON"])

def extract_tasks(value):
"""Return a flat task list and fail for any unknown shape."""
if isinstance(value, list):
if all(isinstance(item, dict) and "state" in item for item in value):
return value
tasks = []
for page in value:
tasks.extend(extract_tasks(page))
return tasks
if isinstance(value, dict):
value = value.get("tasks", value.get("items"))
if isinstance(value, list):
return extract_tasks(value)
raise SystemExit("Unsupported agent-task response shape")

active_states = {"queued", "in_progress", "idle", "waiting_for_user"}
terminal_states = {"completed", "failed", "timed_out", "cancelled"}
for task in extract_tasks(payload):
state = task.get("state") if isinstance(task, dict) else None
if state in active_states or state not in terminal_states:
raise SystemExit("An active or unknown agent task already owns the queue")
PY

prompt="$(cat <<'PROMPT'
Continue commercializing ContextualWisdomLab/scopeweave on the develop branch.

The organization-owned PR maintenance schedulers already review, repair,
revalidate, and merge open pull requests more frequently than this hourly
gate. This task is created only after both pull requests and active agent
tasks are confirmed to be zero. Select exactly one highest-impact
buyer-visible product gap that fits one bounded pull request.

First inspect AGENTS.md, README.md, CHANGELOG.md, docs/doctoring,
operations documentation, open issues, recent commits, package and service
boundaries, database schema, tests, security posture, accessibility,
interoperability with ContextualWisdomLab/.github, naruon, Clearfolio, and
contextual-orchestrator, and the end-to-end project-management user journey.
Preserve standalone operation while keeping seams suitable for modular MSA
extraction and provider-neutral adapters.

Work test-first: write a failing realistic test before production code and
verify that it fails for the intended reason. Implement the smallest coherent
vertical slice, then run focused and full validation. Require 100% production
statement, branch, function, and line coverage for every new or changed
production module and complete beginner-readable JSDoc/docstrings. Preserve
fail-closed input contracts, tenant isolation, bounded resource use,
deterministic behavior where applicable, immutable audit evidence, and
two-or-more-word snake_case database object names. Do not silently create or
retain single-word database objects.

Use the latest authoritative international standard, official primary
specification, or peer-reviewed paper for every material technical or
methodological decision. Record the decision, limitations, and APA 7th
references under docs/doctoring/. Do not claim compliance or certification
beyond executable evidence.

Tests must represent realistic ScopeWeave behavior: multi-tenant projects,
concurrent edits or requests, partial downstream failure, restart and
migration behavior, large WBS inputs, accessibility, rollback, and customer-
visible output as appropriate to the selected slice. If an LLM-dependent test
is genuinely necessary, use the NVIDIA_NIM_API_KEY repository secret and the
contextual-orchestrator boundary rather than embedding a provider-specific
client. Keep deterministic non-LLM tests as the required merge gate.

Use Figma or Product Design only when the selected slice has an actual buyer-facing UI;
capture loading, empty, error, keyboard, screen-reader, touch, narrow viewport,
and permission states before implementation. Do not add cosmetic UI to a
backend-only or library-only slice.

Update CHANGELOG.md, operator and architecture documentation, migrations,
rollback evidence, package smoke tests, and version metadata when the slice is
genuinely release-ready. Open exactly one focused pull request explaining
buyer impact, standards evidence, compatibility, risks, and executable
verification. Do not merge your own pull request, publish a release, weaken
branch protection, or bypass required checks and independent review.
PROMPT
)"

payload="$(
jq -n \
--arg prompt "$prompt" \
--arg base_ref "$BASE_BRANCH" \
'{prompt: $prompt, base_ref: $base_ref, create_pull_request: true}'
)"
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"/agents/repos/${TARGET_REPOSITORY}/tasks" \
--input - <<<"$payload"
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Added a fail-closed hourly product-development gate that creates at most one
buyer-visible Copilot agent task only when both the pull-request queue and
active/unknown agent-task queue are empty. The repository workflow keeps
read-only GitHub permissions, requires a separately scoped
`COPILOT_GITHUB_TOKEN`, and leaves PR review, repair, revalidation, and merge
ownership in the organization-central `.github` workflows.
- Added deterministic PM analysis for requirements/RFI/RFP readiness, WBS
estimation coverage, dependency risk, and procurement package section checks.
- Preserved PM-analysis research papers, NASA WBS handbook, BCP 14, and JSON
Expand Down Expand Up @@ -61,4 +67,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.0.1] - 2026-06-25
### 성능 개선 (Performance)
- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다.
- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다.
131 changes: 131 additions & 0 deletions docs/doctoring/hourly-product-development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Hourly product-development orchestration: evidence and design record

## Decision

ScopeWeave separates privileged pull-request maintenance from product-task
creation. Organization-central workflows own review dispatch, feedback repair,
exact-head checks, branch updates, and policy-compliant merge behavior. The
repository-level hourly gate creates one development task only after it proves
that no pull request and no active or unknown agent task owns the queue.

This boundary prevents three failure modes:

1. duplicated repository and organization PR schedulers consuming the same
Actions and reviewer capacity;
2. overlapping coding agents creating competing pull requests for the same
buyer Gap; and
3. an orchestration token receiving repository write permissions that it does
not require.

## Authoritative platform constraints

GitHub documents the Agent Tasks API as a public-preview interface that can
create, list, and inspect Copilot cloud-agent tasks. The API accepts
`base_ref` and `create_pull_request`, but supports user-to-server credentials
rather than the ordinary workflow `GITHUB_TOKEN`. ScopeWeave therefore uses a
separately scoped `COPILOT_GITHUB_TOKEN`, inventories tasks before creation,
and fails closed when the credential, API call, response shape, or task state
cannot be trusted. The current Agent Tasks endpoint examples explicitly send
`X-GitHub-Api-Version: 2022-11-28`; the workflow follows that endpoint-specific
documented contract instead of opting the preview integration into the newer
general REST version without endpoint evidence.

GitHub scheduled workflows execute from the latest commit on the default branch.
Consequently, the hourly gate does not become active merely because its pull
request exists: the workflow must first pass protected-branch review and merge
into `develop`.

GitHub Actions concurrency groups prevent multiple runs with the same key from
running simultaneously. ScopeWeave uses one repository-wide group and sets
`cancel-in-progress: false` so a later hourly tick cannot interrupt a running
inventory between the first and second duplicate-prevention checks. Because
GitHub may replace an older pending run, the workflow does not depend on strict
cron ordering or on every scheduled tick being executed.

GitHub's workflow-security guidance recommends explicit minimum permissions.
The hourly workflow declares only read access to repository contents and pull
requests. It does not run a third-party action, inherit all secrets, or grant
write access through `GITHUB_TOKEN`; the user token is exposed only to the two
Agent Tasks API steps.

## Fail-closed task inventory

The gate recognizes these active states:

- `queued`
- `in_progress`
- `idle`
- `waiting_for_user`

It recognizes only these terminal states:

- `completed`
- `failed`
- `timed_out`
- `cancelled`

Every unknown state, non-object entry, unsupported pagination shape, parse
failure, HTTP failure, missing token, or open PR is treated as ownership of the
queue. A second inventory immediately before creation narrows the race window.
The API does not expose a repository-level compare-and-create primitive in the
documented public-preview contract, so the workflow does not claim globally
atomic task creation against unrelated external clients. Repository operators
must use this workflow as the single scheduled producer.

## Development contract

The task prompt is a merge gate rather than a marketing statement. It requires
one bounded buyer-visible vertical slice, failing-test-first development,
realistic customer and failure cases, standalone and modular MSA operation,
provider-neutral adapters, two-or-more-word `snake_case` database objects,
complete JSDoc/docstrings, and 100% statement, branch, function, and line
coverage for changed production modules.

Material decisions require current authoritative standards or peer-reviewed
evidence recorded with APA 7th references under `docs/doctoring/`. LLM-dependent
tests are optional rather than default; when necessary they use the repository's
`NVIDIA_NIM_API_KEY` through the `contextual-orchestrator` seam, while
deterministic tests remain required. Figma or Product Design is required only
when the selected slice includes an actual buyer-facing interface.

The generated task must open one focused pull request and is expressly forbidden
from merging itself, publishing a release, weakening branch protection, or
bypassing checks and independent review.

## Verification contract

`tests/config/hourly-product-development.test.mjs` statically proves:

- the hourly schedule and manual entry point;
- one non-cancelling concurrency group;
- read-only repository permissions;
- absence of central PR-scheduler duplication;
- required user-token, documented API-version, and Agent Tasks API boundaries;
- open-PR and active/unknown-task rejection;
- one reviewable pull request per eligible task;
- coverage, documentation, database naming, realistic testing, standards,
Figma, LLM-provider, and no-self-merge prompt requirements; and
- absence of broad secret inheritance or repository write permissions.

After merge, an operator must exercise `workflow_dispatch` once with no open PR
and a controlled terminal task inventory, confirm exactly one task is created,
then repeat with that task active and confirm no second task is created. The
production schedule remains fail-closed until `COPILOT_GITHUB_TOKEN` is
configured with the documented user-to-server Agent Tasks permissions.

## References

GitHub. (n.d.-a). *Concurrency*. GitHub Docs. Retrieved August 4, 2026, from
https://docs.github.com/en/actions/concepts/workflows-and-actions/concurrency

GitHub. (n.d.-b). *Events that trigger workflows*. GitHub Docs. Retrieved August
4, 2026, from
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows

GitHub. (n.d.-c). *Protecting against security threats*. GitHub Docs. Retrieved
August 4, 2026, from
https://docs.github.com/en/code-security/tutorials/secure-your-organization/protect-against-threats

GitHub. (n.d.-d). *Using Copilot cloud agent via the API*. GitHub Docs.
Retrieved August 4, 2026, from
https://docs.github.com/en/copilot/how-tos/use-copilot-agents/cloud-agent/use-cloud-agent-via-the-api
Loading
Loading