01関数の型と docstring の入出力例を先に書き、本体を Copilot に補完させる確認済
import 文、関数名、引数と戻り値の型、docstring の入出力の例を先に書いてから補完させると、使うライブラリと期待する結果を Copilot に伝えやすい。docstring の例は doctest でそのまま試験になり、生成された本体が例のとおりに動くかをすぐ確かめられる。
- ファイルの先頭に、使うライブラリの import を書く
- 関数名、引数と戻り値の型、目的を 1 行で書いた docstring を用意する
- docstring に >>> の例を、普通の入力と境界の入力(全角、余分な語、該当なし)の両方で書く
- 本体を Copilot に補完させ、提案を読んで意味が分かってから受け入れる
- doctest で例を実行し、失敗した例を指示に書き足して作り直させる
"""Skeleton given to Copilot (imports, signature, docstring examples); body kept after review."""
import re
import unicodedata
PATTERN = re.compile(r"R-?(\d{4})-(\d{1,3})")
def normalize_request_id(text: str) -> str | None:
"""Return a request ID as R-YYYY-NNN, or None when the text contains no ID.
>>> normalize_request_id("r-2026-5")
'R-2026-005'
>>> normalize_request_id("\uff32\uff0d\uff12\uff10\uff12\uff16\uff0d\uff10\uff14\uff12 proposal")
'R-2026-042'
>>> normalize_request_id("see R2026-7 for details")
'R-2026-007'
>>> normalize_request_id("no id here") is None
True
"""
t = unicodedata.normalize("NFKC", text or "").upper()
m = PATTERN.search(t)
if not m:
return None
return "R-%s-%03d" % (m.group(1), int(m.group(2)))
if __name__ == "__main__":
import doctest
print("doctest:", doctest.testmod())実行結果(2026-09-12)
doctest: TestResults(failed=0, attempted=4)この docstring の例をすべて満たすように normalize_request_id の本体を書いて。使ってよいのは re と unicodedata だけ。全角の英数字と記号は NFKC で半角にし、番号は 3 桁にそろえること。
例に無い入力の扱いは Copilot が推測で決める。業務で出る書式を例に足してから任せる。例がすべて通っても、例の外で正しいとは限らないので、実データの一部でも試す。doctest は表示の文字列を比べるので、出力の書き方が変わると失敗する。
出典の該当箇所
Use examples to help Copilot understand what you want. You can provide example input data, example outputs, and example implementations.
If you want to use a specific library, set the import statements at the top of the file or specify what library you want to use.
To check that a module’s docstrings are up-to-date by verifying that all interactive examples still work as documented.
02先に assert の試験を書き、それを満たす関数を Copilot に書かせる確認済
GitHub の文書は、関数を書く前に単体テストを用意し、そのテストが表す関数を Copilot に書かせる方法を示している。日付の書き方のばらつきのように、文章より例で決めた方が早い仕様に向く。試験を実行し、全部通るまで指示と生成を繰り返す。
- 受け付ける書き方と、受け付けない書き方(年が無い、存在しない日付、空欄)を表にする
- 表を (入力, 期待値) の組にし、assert で比べる試験関数を書く
- Copilot に「この試験をすべて通す関数を書いて」と頼み、使ってよいモジュールも指定する
- 試験を実行し、失敗した組を示して直させる。試験の側は Copilot に書き換えさせない
- 業務で新しい書き方が見つかったら、まず試験に足してから関数を直す
"""Tests written first; the function was then generated from them and kept after review."""
import re
from datetime import date
TESTS = [
("2026-09-12", date(2026, 9, 12)),
("2026/9/1", date(2026, 9, 1)),
("2026\u5e749\u670812\u65e5", date(2026, 9, 12)), # 2026 nen 9 gatsu 12 nichi
(" 2026.09.12 ", date(2026, 9, 12)),
("9/12", None), # no year: do not guess
("2026-02-30", None), # not a real date
("", None),
]
def parse_date(text):
m = re.fullmatch(r"\s*(\d{4})[-/.\u5e74](\d{1,2})[-/.\u6708](\d{1,2})\u65e5?\s*", text or "")
if not m:
return None
try:
return date(*map(int, m.groups()))
except ValueError:
return None
def test_parse_date():
for text, want in TESTS:
got = parse_date(text)
assert got == want, (text, got, want)
if __name__ == "__main__":
test_parse_date()
print("all", len(TESTS), "tests passed")実行結果(2026-09-12)
all 7 tests passed下の TESTS をすべて通す parse_date(text) を書いて。使ってよいのは re と datetime だけ。年が無いものと存在しない日付は None を返し、推測で補わないこと。試験の表は変えないこと。
試験に無い書き方の扱いは保証されない。和暦や「来週月曜」のような書き方は、別に扱うか人が確かめる。Copilot が試験に合わせて特定の値だけを特別扱いするコードを書くことがあるので、本体を読んで確かめる。
出典の該当箇所
Unit tests can also serve as examples. Before writing your function, you can use Copilot to write unit tests for the function. Then, you can ask Copilot to write a function described by those unit tests.
Generating tests for test-driven development
03大きな処理は小さな関数に分け、1 つずつ指示して組み立てる確認済
「依頼台帳を集計して報告を作る」を一度に頼まず、読み込み、絞り込み、集計、書き出しの関数に分けて 1 つずつ書かせる。各関数の直後に assert を置くと、結果がずれたときにどの段で起きたかが分かる。最後に、前の関数を使ってつなぐ関数を頼む。
- 処理を、読む・絞る・数える・書き出すの 4 段に分け、段ごとに入力と出力の形を 1 行で書く
- 1 つ目の指示で読み込みの関数を書かせ、列名が想定どおりかを assert で確かめる
- 次の指示では「前の関数の戻り値(辞書のリスト)を受け取る」と明記して、絞り込みと集計を書かせる
- 各段の直後に assert を 1 行ずつ置き、見本のデータで実行する
- 最後に、前の関数を順に呼ぶ main を書かせ、件数が見本と合うかを見る
"""Request-ledger report built from small functions, each written from its own prompt."""
import csv
from collections import Counter
STATUS, DEPT, DONE = "\u72b6\u614b", "\u4f9d\u983c\u90e8\u7f72", "\u5b8c\u4e86" # column names / done value
def load_rows(path): # prompt 1
with open(path, encoding="utf-8-sig", newline="") as f:
return list(csv.DictReader(f))
def open_items(rows): # prompt 2
return [r for r in rows if r[STATUS] != DONE]
def count_by(rows, column): # prompt 3
return Counter(r[column] for r in rows)
def write_report(counter, path): # prompt 4
with open(path, "w", encoding="utf-8-sig", newline="") as f:
w = csv.writer(f)
w.writerow(["key", "count"])
w.writerows(counter.most_common())
def main(): # last prompt: use the previous functions
rows = load_rows("samples/ledger.csv")
assert rows and STATUS in rows[0] and DEPT in rows[0], "unexpected columns"
todo = open_items(rows)
assert all(r[STATUS] != DONE for r in todo)
by_dept = count_by(todo, DEPT)
assert sum(by_dept.values()) == len(todo)
write_report(by_dept, "out_open_by_dept.csv")
print("rows:", len(rows), "open:", len(todo))
for dept, n in by_dept.most_common():
print(dept, n)
if __name__ == "__main__":
main()実行結果(2026-09-12)
rows: 40 open: 26
広報部 14
営業部 7
人事部 5(1 つ目)依頼台帳の CSV(UTF-8 BOM 付き)を読んで辞書のリストを返す load_rows(path) を書いて。(2 つ目)load_rows の戻り値を受け取り、状態が「完了」でない行だけを返す open_items(rows) を書いて。(3 つ目)列名を受け取って件数を数える count_by(rows, column) を書いて。(最後)前の関数を使って、未完了の件数を依頼部署ごとに CSV に書く main を書いて。
段に分けても、段と段の間の約束(列名、型、空欄の扱い)がずれると結果は誤る。約束は各指示に毎回書く。状態の値の書き方が台帳の中でそろっているかを先に確かめる。集計の結果は、元の台帳の件数と突き合わせてから使う。
出典の該当箇所
If you want Copilot to complete a complex or large task, break the task into multiple simple, small tasks.
Write a function that uses the previous functions to generate a 10 by 10 grid of letters that contains at least 10 words.
04.github/copilot-instructions.md にリポジトリ共通の約束を書く確認済
リポジトリのルートに .github/copilot-instructions.md を置くと、そのリポジトリでの Copilot への要求に指示が自動で加わる。ID は環境変数から読む、書き込みは既定で DRY_RUN、権限は最小、といった毎回の約束を、短く独立した文で書く。
- リポジトリのルートに .github フォルダーを作り、copilot-instructions.md を置く
- 指示は Markdown の自然文で書く。指示の間の空白や空行は無視される
- すべての要求に送られるので、どの作業にも当てはまることだけを書く。個別の作業の手順は書かない
- Copilot Chat の回答の参照一覧に .github/copilot-instructions.md が出ていれば、指示が使われている
- 個人の指示が最も優先され、次にリポジトリ、最後に組織の指示。矛盾する指示を置かない
- 作業ごとの定型の指示は、IDE ではプロンプトファイル(*.prompt.md、パブリックプレビュー)に分けられる
# Project overview
Python scripts that read Microsoft 365 data through the Microsoft Graph SDK for Python and write reports to CSV files.
## Security rules
- Read the tenant ID, client ID and all other IDs from environment variables (os.environ). Never write IDs, secrets, tokens or webhook URLs in code.
- For app-only access use a certificate or a managed identity (azure-identity), never a client secret.
- Request the least privileged Graph permission shown on the API page. Read-only scripts must not request ReadWrite permissions.
- Use $select so that only the fields the script needs are fetched.
- Never print or log access tokens, message bodies or e-mail addresses.
## Writes
- Code that sends, updates or uploads anything must define DRY_RUN = os.environ.get("DRY_RUN", "1") != "0" and, when DRY_RUN is true, only print what it would do.
- Do not write code that deletes data.
## Style and tests
- Keep comments short and in English.
- Put logic in small functions and add an assert-based test for each. Tests must run without signing in or using the network.
- On HTTP 429, wait for the Retry-After seconds before retrying.指示は Copilot が参考にするもので、守られたかは生成されたコードを読んで確かめる。秘密情報や社内の URL を指示のファイルに書かない(リポジトリを見られる人は全員読める)。外部の資料を参照させる指示や、回答の長さを縛る指示は、期待どおりに働かないことがあると文書にある。
出典の該当箇所
In the root of your repository, create a file named .github/copilot-instructions.md.
Custom instructions consist of natural language instructions and are most effective when they are short, self-contained statements.
Because the instructions are sent with every chat message, they should be broadly applicable to most requests you will make in the context of the repository.
Personal instructions take the highest priority. Repository instructions come next, and then organization instructions are prioritized last.
Prompt files are only available in VS Code, Visual Studio, and JetBrains IDEs.
05特定のフォルダーだけの指示は .github/instructions/*.instructions.md に分ける確認済
一部のファイルにだけ当てはまる指示は、.github/instructions の下に名前が .instructions.md で終わるファイルを作り、先頭の applyTo に glob を書く。該当するファイルを扱うときは、リポジトリ全体の指示と両方が使われるので、全体の指示を短く保てる。
- .github/instructions フォルダーを作る。サブフォルダーで整理してもよい
- 目的の分かる名前で NAME.instructions.md を作る(例: graph-scripts.instructions.md)
- 先頭のフロントマターに applyTo を書く。複数のパターンはカンマで区切る
- GitHub.com で Copilot code review に使わせたくない指示は、フロントマターに excludeAgent: "code-review" を足す
- 優先順位はパス別の指示が全体の指示より上。両者が矛盾しないかを見直す
---
applyTo: "scripts/graph/**/*.py,tests/graph/**/*.py"
---
# Graph scripts
- Build credentials with azure-identity: DeviceCodeCredential for delegated scripts, CertificateCredential or ManagedIdentityCredential for app-only jobs.
- Declare the permissions the script needs in a SCOPES constant at the top of the file, using the least privileged one listed on the API page.
- Pass $select with only the properties used later in the script.
- Keep API calls in one async function; keep filtering and counting in plain functions that take lists of dicts, so tests can feed them sample JSON.
- Tests under tests/graph must not sign in or use the network; load the JSON files in tests/graph/samples instead.GitHub.com では、パス別の指示は現時点で Copilot cloud agent と Copilot code review だけが対応する。VS Code などの IDE での扱いは、IDE 向けの文書で確かめる。glob が合っていないと指示は使われないので、対象のファイルで Chat の参照一覧を見て確かめる。
出典の該当箇所
Create one or more NAME.instructions.md files, where NAME indicates the purpose of the instructions. The file name must end with .instructions.md.
Currently, on GitHub.com, path-specific custom instructions are only supported for Copilot cloud agent and Copilot code review.
If the path you specify matches a file that Copilot is working on, and a repository-wide custom instructions file also exists, then the instructions from both files are used.
VS Code supports three types of repository custom instructions.
06生成されたコードを ast で機械的に点検してから、人が読む確認済
GitHub は、Copilot の提案を理解してから使うことと、試験やリンターなどの道具で確かめることを勧めている。Python の ast で、eval と exec の呼び出し、timeout の無い requests の呼び出し、秘密情報らしい名前への文字列の代入を探し、人が読む前に指摘を一覧にする。
- 生成されたコードはファイルに保存し、実行せずに ast.parse で構文木にする
- ast.walk で全部の節点をたどり、関数の呼び出しと代入だけを見る
- 規則を 3 つ置く: eval・exec・compile の呼び出し、timeout の無い requests の呼び出し、secret・password・token などの名前への文字列の代入
- 指摘は行番号付きで一覧にし、人が読むときの手がかりにする
- 見本のファイルに、見つけるべき例と見つけてはいけない例(timeout 付きの呼び出し)を両方入れて、点検の関数を試す
"""Screen generated Python for risky patterns before a human review (ast only; nothing is run)."""
import ast
import sys
RISKY_CALLS = {"eval", "exec", "compile"}
HTTP_VERBS = {"get", "post", "put", "patch", "delete", "request"}
SECRET_WORDS = ("secret", "password", "passwd", "token", "api_key", "apikey")
def findings(source, name="<generated>"):
out = []
for node in ast.walk(ast.parse(source, name)):
if isinstance(node, ast.Call):
f = node.func
if isinstance(f, ast.Name) and f.id in RISKY_CALLS:
out.append((node.lineno, "call to %s()" % f.id))
if (isinstance(f, ast.Attribute) and f.attr in HTTP_VERBS
and isinstance(f.value, ast.Name) and f.value.id == "requests"
and not any(k.arg == "timeout" for k in node.keywords)):
out.append((node.lineno, "requests.%s without timeout" % f.attr))
if (isinstance(node, ast.Assign) and isinstance(node.value, ast.Constant)
and isinstance(node.value.value, str) and len(node.value.value) >= 8):
for t in node.targets:
if isinstance(t, ast.Name) and any(w in t.id.lower() for w in SECRET_WORDS):
out.append((node.lineno, "string literal assigned to %s" % t.id))
return sorted(out)
def main(path):
with open(path, encoding="utf-8") as f:
found = findings(f.read(), path)
for line, msg in found:
print("line %d: %s" % (line, msg))
print("findings:", len(found))
if __name__ == "__main__":
main(sys.argv[1] if len(sys.argv) > 1 else "samples/security_generated_snippet.txt")実行結果(2026-09-12)
line 5: string literal assigned to api_key
line 10: requests.get without timeout
line 14: call to eval()
findings: 3Python のソースを文字列で受け取り、ast だけで点検して (行番号, 指摘) のリストを返す findings(source) を書いて。規則は 3 つ: eval・exec・compile の呼び出し、requests.get などで timeout 引数が無いもの、secret・password・token・api_key を含む名前への 8 文字以上の文字列の代入。コードは実行しないこと。
この点検は決まった形しか見つけない。別名での import や、辞書の中の秘密情報は見逃す。指摘が 0 件でも安全とは言えないので、人が読むこと、試験を動かすこと、組織のコードスキャンを使うことは省かない。点検の規則は、社内で起きた誤りに合わせて足していく。
出典の該当箇所
Use automated tests and tooling to check Copilot's work. With the help of tools like linting, code scanning, and IP scanning, you can automate an additional layer of security and accuracy checks.
Understand suggested code before you implement it.
Recursively yield all descendant nodes in the tree starting at node