Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dbdb482
feat(md2hwp): add collaboration infrastructure and template injection…
baekho-lim Mar 2, 2026
f9402bb
docs: add collaboration workflow to CLAUDE.md + refine AGENTS.md
baekho-lim Mar 2, 2026
8ecbbd6
feat(md2hwp): add XML tree navigation helper functions
baekho-lim Mar 2, 2026
83424d1
feat(md2hwp): rewrite section replacements with cell-scoped clearing
baekho-lim Mar 2, 2026
6647de3
feat(md2hwp): rewrite table cell fills with cellAddr lookup
baekho-lim Mar 2, 2026
51c107d
feat(md2hwp): enhance inspect with table context and table mode
baekho-lim Mar 2, 2026
66e4305
Merge pull request #8 from baekho-lim/codex/phase1-p0-fill-engine-imp…
baekho-lim Mar 2, 2026
97daa93
feat(md2hwp): add multi-paragraph content injection
baekho-lim Mar 2, 2026
b7ee42d
feat(md2hwp): add analyze mode for template schema extraction
baekho-lim Mar 2, 2026
7fed4a8
Merge pull request #9 from baekho-lim/codex/phase2-p1-fill-engine-imp…
baekho-lim Mar 2, 2026
b22f28c
fix(md2hwp): prevent fallback table fill from overwriting body text
baekho-lim Mar 3, 2026
2936706
fix(md2hwp): validate fill plan selectors and paragraph lists
baekho-lim Mar 3, 2026
7512fa6
fix(md2hwp): scope cell traversal to direct table descendants
baekho-lim Mar 3, 2026
5b40f1c
test(md2hwp): add pytest suite for fill engine
baekho-lim Mar 3, 2026
6ac7160
test(md2hwp): add sample fill plan and e2e fill coverage
baekho-lim Mar 3, 2026
09b8480
Merge pull request #15 from baekho-lim/codex/phase3-bugfixes-and-tests
baekho-lim Mar 3, 2026
f3858b6
chore(automation): add pm-dev loop workflows and guardrails
baekho-lim Mar 3, 2026
53fac3d
Merge pull request #19 from baekho-lim/codex/automation-pm-dev-loop
baekho-lim Mar 3, 2026
2296a33
fix(md2hwp): scope fallback table-cell scan to label table
baekho-lim Mar 3, 2026
02f1e7b
test(md2hwp): add validation and nested-table regression tests
baekho-lim Mar 3, 2026
986a928
refactor(md2hwp): split table fill and table inspect helpers
baekho-lim Mar 3, 2026
8ac7127
Merge pull request #23 from baekho-lim/codex/16-17-18-fallback-tests-…
baekho-lim Mar 3, 2026
8506d1c
fix(md2hwp): reduce analyze placeholder false positives
baekho-lim Mar 3, 2026
7e979fb
test(md2hwp): harden multi-paragraph e2e XML assertions
baekho-lim Mar 3, 2026
76d352e
Merge pull request #24 from baekho-lim/codex/20-22-analyze-e2e-hardening
baekho-lim Mar 3, 2026
5cf6f23
fix(hwpx): preserve table-cell paragraph boundaries in markdown
baekho-lim Mar 3, 2026
628f22b
Merge pull request #26 from baekho-lim/codex/25-preserve-table-cell-p…
baekho-lim Mar 3, 2026
673b93a
test(e2e): add table-cell multi-paragraph markdown regression
baekho-lim Mar 3, 2026
94d01fa
feat(md2hwp): add schema compilation pipeline for 재도전성공패키지
baekho-lim Mar 15, 2026
64bcc20
chore: add __pycache__, .omx, md2hwp-outputs to .gitignore
baekho-lim Mar 15, 2026
ab2cad0
chore: restore testdata fixtures, exclude personal business plan from…
baekho-lim Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/ISSUE_TEMPLATE/task.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: Implementation Task
about: AI-consumable task with full context and acceptance criteria
title: ''
labels: 'task,backlog'
assignees: ''
---

## Problem

<!-- What is broken or missing? -->

## Spec

<!-- Exact interface, behavior, function signatures -->

## Out of Scope

<!-- Explicitly list what this issue must NOT change -->

## Files to Modify

<!-- List of files with brief description of changes -->

## Test Plan

<!-- Expected inputs/outputs, test fixtures, how to verify -->

## Acceptance Criteria

- [ ] Behavior matches Spec exactly
- [ ] Existing tests pass
- [ ] New tests cover the change
- [ ] No regression in related CLI features

## Automation Policy

<!-- Guardrails for PM/Dev automation loop -->
- Max automated attempts: 3
- If same failure repeats 2+ times, add `needs-human` and stop auto-retry
- Do not auto-merge without Claude approval + CI green

## References

<!-- Links to design docs, related issues, code snippets -->
30 changes: 30 additions & 0 deletions .github/automation/OPERATING_MODEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# PM/Dev Automation Operating Model

## Roles
- Claude PM: issue definition, review decision, prioritization.
- Codex Dev: implementation, tests, PR delivery.

## State Machine
- `backlog` -> `ready-codex` -> `in-progress` -> `needs-claude-review` -> `approved` -> `done`
- Exception states: `blocked`, `needs-human`

## Automation Jobs
- `automation-label-bootstrap.yml`: ensures required labels exist.
- `automation-state-machine.yml`:
- issue enters execution when labeled `ready-codex`
- PRs are labeled `needs-claude-review`
- merged PRs are labeled `done`
- optional follow-up issue generation when PR has `auto-loop`
- `claude-review-scheduler.yml`:
- reminds pending Claude review every 6h (max 3 reminders)
- escalates to `needs-human` + `blocked` after max reminders

## Loop Prevention Rules
- Max automated issue attempts: 3
- Max review reminders: 3
- Escalate to `needs-human` when thresholds are exceeded
- Follow-up issue creation is opt-in via `auto-loop` label only

## Merge Gate
- Required: CI green + Claude approval
- Do not auto-merge when PR is `blocked` or `needs-human`
25 changes: 25 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Summary

<!-- What changed and why -->

## Linked Issues

<!-- Use closing keywords: Closes #123 -->

## Verification

<!-- Exact commands and key outputs -->
- [ ] Tests passed locally
- [ ] CI passed
- [ ] No unrelated files changed

## Risks

<!-- Behavior/regression risks and mitigations -->

## Handoff To Claude PM

<!-- Keep this concise and actionable -->
- Review focus:
- Open questions:
- Suggested next issue:
63 changes: 63 additions & 0 deletions .github/workflows/automation-label-bootstrap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Automation Label Bootstrap

on:
workflow_dispatch:
schedule:
- cron: "17 3 * * 1"

permissions:
issues: write
pull-requests: write

jobs:
bootstrap-labels:
runs-on: ubuntu-latest
steps:
- name: Ensure automation labels exist
uses: actions/github-script@v7
with:
script: |
const labels = [
{ name: 'task', color: '0E8A16', description: 'Tracked implementation task' },
{ name: 'backlog', color: 'BFD4F2', description: 'Queued but not ready for execution' },
{ name: 'ready-codex', color: '1D76DB', description: 'Ready for Codex execution' },
{ name: 'in-progress', color: 'FBCA04', description: 'Actively being implemented' },
{ name: 'needs-claude-review', color: '5319E7', description: 'Awaiting Claude PM review' },
{ name: 'approved', color: '0E8A16', description: 'Approved for merge' },
{ name: 'blocked', color: 'D93F0B', description: 'Blocked by dependency or risk' },
{ name: 'needs-human', color: 'B60205', description: 'Automation halted, human decision required' },
{ name: 'done', color: 'C2E0C6', description: 'Completed and verified' },
{ name: 'auto-loop', color: '0052CC', description: 'Opt-in: create follow-up PM issue after merge' },
{ name: 'ready-claude', color: '5319E7', description: 'Ready for Claude PM planning/review' },
];

for (const label of labels) {
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
});
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
core.info(`Updated label: ${label.name}`);
} catch (error) {
if (error.status === 404) {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
core.info(`Created label: ${label.name}`);
} else {
throw error;
}
}
}
227 changes: 227 additions & 0 deletions .github/workflows/automation-state-machine.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
name: Automation State Machine

on:
issues:
types: [labeled, reopened]
pull_request:
types: [opened, reopened, synchronize, ready_for_review, closed]

permissions:
issues: write
pull-requests: write
contents: read

jobs:
issue-ready-codex:
if: |
github.event_name == 'issues' &&
(
(github.event.action == 'labeled' && github.event.label.name == 'ready-codex') ||
github.event.action == 'reopened'
)
runs-on: ubuntu-latest
steps:
- name: Transition issue into execution state
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const issueNumber = issue.number;
const maxAttempts = 3;
const labels = issue.labels.map(l => l.name);

if (!labels.includes('ready-codex')) {
core.info('Issue is not ready-codex; skipping.');
return;
}

const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});

const attemptMarker = '<!-- codex-attempt -->';
const attempts = comments.filter(c => c.body && c.body.includes(attemptMarker)).length;

const labelSet = new Set(labels);
const applyLabels = async (nextSet) => {
await github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [...nextSet],
});
};

if (attempts >= maxAttempts) {
labelSet.delete('ready-codex');
labelSet.delete('in-progress');
labelSet.add('blocked');
labelSet.add('needs-human');
await applyLabels(labelSet);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: [
'<!-- codex-stop -->',
'Automation halted: reached max automated attempts (3).',
'Please perform human triage and then relabel with `ready-codex` if retry is still valid.',
].join('\n'),
});
return;
}

labelSet.add('in-progress');
labelSet.delete('backlog');
labelSet.delete('blocked');
labelSet.delete('needs-human');
labelSet.delete('done');
await applyLabels(labelSet);

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: [
'<!-- codex-attempt -->',
`Codex execution started (attempt ${attempts + 1}/${maxAttempts}).`,
'Next transition expected: `PR_OPEN` -> `needs-claude-review`.',
].join('\n'),
});

pr-review-request:
if: |
github.event_name == 'pull_request' &&
contains(fromJSON('["opened","reopened","synchronize","ready_for_review"]'), github.event.action)
runs-on: ubuntu-latest
steps:
- name: Mark PR as pending Claude review
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const prNumber = pr.number;
const labels = new Set((pr.labels || []).map(l => l.name));
labels.add('needs-claude-review');
labels.delete('approved');
labels.delete('done');

await github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [...labels],
});

const sha = pr.head.sha;
const marker = `<!-- claude-review-request:${sha} -->`;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
});
const alreadyPosted = comments.some(c => c.body && c.body.includes(marker));
if (alreadyPosted) {
core.info('Review request already posted for this SHA.');
return;
}

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: [
marker,
'Codex implementation update is ready for Claude PM review.',
'Review gate: spec match, risk checks, and merge/no-merge decision.',
].join('\n'),
});

pr-merged-closeout:
if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Close out merged PR and optionally create follow-up issue
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const prNumber = pr.number;
const labels = new Set((pr.labels || []).map(l => l.name));
const hasAutoLoop = labels.has('auto-loop');

labels.add('done');
labels.delete('needs-claude-review');
labels.delete('in-progress');
labels.delete('ready-codex');
labels.delete('blocked');

await github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [...labels],
});

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: [
'<!-- codex-complete -->',
`Completed and merged in ${pr.merge_commit_sha}.`,
'State transition: `MERGED -> DONE`.',
].join('\n'),
});

const body = pr.body || '';
const refs = [...body.matchAll(/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/ig)]
.map(m => Number(m[1]))
.filter(n => Number.isFinite(n));
const uniqueRefs = [...new Set(refs)];

for (const issueNumber of uniqueRefs) {
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: `Implemented by PR #${prNumber} and merged in \`${pr.merge_commit_sha}\`.`,
});
} catch (error) {
core.warning(`Failed to comment on issue #${issueNumber}: ${error.message}`);
}
}

if (!hasAutoLoop) {
core.info('auto-loop label absent; skipping follow-up PM issue creation.');
return;
}

const followUpTitle = `pm: post-merge review for PR #${prNumber}`;
const followUpBody = [
`Source PR: #${prNumber}`,
'',
'## Claude PM Review',
'- Validate merged behavior against acceptance criteria',
'- Identify residual risks and missing tests',
'- Decide whether a new Codex issue is required',
'',
'## Next Action',
'- If more work is needed, create a new issue and label it `ready-codex`',
'- If no further action is needed, close this issue',
'',
'<!-- auto-loop-followup -->',
].join('\n');

await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: followUpTitle,
body: followUpBody,
labels: ['task', 'ready-claude', 'backlog'],
});
Loading